0%

产生背景

在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。 但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员来说,很不方便。于是,就想到是不是可以在一个系统登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。

阅读全文 »

DispatcherServlet

Web MVC Config

应用程序可以声明所需的在 Special Bean Types 中列出的基础bean来处理请求。DispatcherServlet 会检查每个特殊bbean的 WebApplicationContext。如果没有匹配的bean类型,它会使用{spring-framework-main-code}/spring-springmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties[DispatcherServlet.properties]中列出默认类型。

在大多数情况下,MVC Config 是最好的起点。它在 Java 或 XML 中声明了所需的 bean,并提供了一个更高级别的配置回调 API 来自定义它。

✏️ Spring Boot 依赖于 MVC Java 配置来配置 Spring MVC,并提供了许多额外方便的选项。

Servlet Config

在 Servlet 3.0+ 环境中,您可以选择以编程方式配置 Servlet 容器作为替代方案或与 web.xml 文件结合使用。

以下示例注册一个 DispatcherServlet

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

WebApplicationInitializer 是 Spring MVC 提供的一个接口,可确保检测到您的实现并自动用于初始化任何 Servlet 3 容器。AbstractDispatcherServletInitializer 是实现了 WebApplicationInitializer 接口的抽象基类,可以通过覆盖其方法来指定 servlet 映射和 DispatcherServlet 配置的位置,使得注册 DispatcherServlet 变得更加容易。

推荐使用基于Java代码的配置方式,示例如下:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

如果你想使用基于XML的配置方式,你可以直接继承 AbstractDispatcherServletInitializer,示例如下:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

AbstractDispatcherServletInitializer 还提供了一种方便的方法来添加 Filter 实例并使它们自动映射到 DispatcherServlet,示例如下:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(),
            new CharacterEncodingFilter()
        };
    }
}

每个过滤器都根据其具体类型添加了一个默认名称,并自动映射到 DispatcherServlet

AbstractDispatcherServletInitializerisAsyncSupported 受保护方法提供了一个单一的地方来启用对 DispatcherServlet 和映射到它的所有过滤器的异步支持。默认情况下,此标志设置为 true。

最后,如果您需要进一步自定义 DispatcherServlet 本身,您可以覆盖 createDispatcherServlet 方法。

Processing

DispatcherServlet处理请求的过程如下:

  1. 找到一个WebApplicationContext用于作为一个属性绑定到请求中,以便于在处理过程中控制器和其他元素能够使用到。(默认绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE中)

  2. 将本地化解析器绑定到请求中,如果不需要可以忽略

  3. 将主题解析器绑定到请求中,让视图等元素决定使用哪个主题,如果不需要而已忽略

  4. 如果你指定了一个multipart file解析器,则会解析请求的multiparts。若能在请求中找到,则会将该请求包装为MultipartHttpServletRequest,以便于处理过程中其他元素进一步处理。有关multipart处理器,请参阅Multipart Resolver

  5. 查找匹配的处理器。如果找到,则执行与处理器(预处理器,后置处理器、控制器)相关联的执行链以准备用于渲染模型。或者,对于带有注解的controller,可以直接响应(在HandlerAdapter)而不是返回视图。

  6. 如果返回的是模型,则将渲染视图。否则不会渲染视图(可能由于预处理器或后置处理器拦截了请求,或处于安全原因),因为请求已经得到合理响应。

可以通过在WebApplicationContext中声明HandlerExceptionResolver beans来解决请求处理过程中抛出的异常。这些异常解析器允许自定义逻辑来解决异常。有关更多详细信息,请参阅Exceptions

Spring DispatcherServlet 还支持返回last-modification-date,如 Servlet API 指定的那样。确定特定请求的最后修改日期的过程很简单:DispatcherServlet 查找适当的处理程序映射并测试找到的处理程序是否实现了LastModified 接口。如果是,则将LastModified接口的long getLastModified(request)方法的值返回给客户端。

您可以通过将Servlet初始化参数(init-param元素)添加到web.xml 文件中的 Servlet 声明来自定义各个DispatcherServlet 实例。下表列出了支持的参数:

Table 1. DispatcherServlet initialization parameters

Parameter Explanation
contextClass 实现 ConfigurableWebApplicationContext 的类,由这个 Servlet 实例化和本地配置。默认情况下,使用 XmlWebApplicationContext
contextConfigLocation 传递给上下文实例(由contextClass 指定)以指示可以找到上下文的位置的字符串。该字符串可能包含多个字符串(使用逗号作为分隔符)以支持多个上下文。在 bean 定义两次的多个上下文位置的情况下,最新的位置优先。
namespace WebApplicationContext 的命名空间。默认为 [servlet-name]-servlet
throwExceptionIfNoHandlerFound 当找不到一个对应请求的处理器是,将抛出NoHandlerFoundException。该异常可以使用HandlerExceptionResolver 捕获(例如,通过使用被标记了@ExceptionHandler的控制器方法)并像其他任何方法一样处理。
默认情况下,这设置为false,在这种情况下 DispatcherServlet 将响应状态设置为 404 (NOT_FOUND) 而不引发异常。
请注意,如果还配置了默认servlet处理器,则未解析的请求始终转发到默认 servlet,并且永远不会引发 404。

Path Matching

ServletAPI会将一个完整的请求路径解析为requestURI,并且进一步分解出contextPathservletPath以及pathInfo。其中具体的值取决于Servlet的映射方式。SpringMVC将通过这些值确定用于映射handler的lookup pathlookup pathDispatcherServlet自身映射中的路径,不包括contextPathservletMapping前缀(如果存在)。

由于servletPathpathInfo会被解码,使得无法直接与requestURI进行比较,如果想要获取lookup path,则需要对requestURI进行解码。由于原本请求路径中可能包含编码的保留字符,使得解码操作将存在破坏原本请求路径的可能,产生安全隐患。

该问题可通过升级到5.3或更高的版本来解决,从5.3版本开始,将使用PathPattern来替代PathMatcher。其通过将路径分段解析,每次仅处理一个分段的解码操作,这样保证了不会存在改变路径结构的风险。PathPattern也支持使用servletPath前缀映射,不过需要保证其中不包含需要编码的字符。

概述

毫无疑问,Spring Cloud 是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术。不过大多数讲解还停留在对 Spring Cloud 功能使用的层面,其底层的很多原理,很多人可能并不知晓。因此本文将通过大量的手绘图,给大家谈谈 Spring Cloud 微服务架构的底层原理。

实际上,Spring Cloud 是一个全家桶式的技术栈,包含了很多组件。本文先从其最核心的几个组件入手,来剖析一下其底层的工作原理。也就是 Eureka、Ribbon、Feign、Hystrix、Zuul 这几个组件。

阅读全文 »

前言

Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为。事务传播行为是 Spring 框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。这是 Spring 为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。但是人们对他的误解也颇多,你一定也听过 “service 方法事务最好不要嵌套” 的传言。要想正确的使用工具首先需要了解工具。本文对七种事务传播行为做详细介绍,内容主要代码示例的方式呈现。

阅读全文 »

前言

Java 提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自 JDK 8 和 Netty 3.10.6)、使用场景进行举例,为读者介绍主流锁的知识点,以及不同的锁的适用场景。

Java 中往往是按照是否含有某一特性来定义锁,我们通过特性将锁进行分组归类,再使用对比的方式进行介绍,帮助大家更快捷的理解相关知识。下面给出本文内容的总体分类目录:

阅读全文 »

添加依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
<dependency>

配置信息

属性 描述
spring.rabbitmq.addresses 逗号分隔的RabbitMQ代理地址列表
spring.rabbitmq.host 代理的主机(默认为localhost)
spring.rabbitmq.port 代理的端口(默认为5672)
spring.rabbitmq.username 访问代理所使用的用户名(可选)
spring.rabbitmq.password 访问代理所使用的密码(可选)
spring.rabbitmq.template.exchange 设置默认的Exchange(可选)
spring.rabbitmq.template.routing-key 设置默认的Routing-Key(可选)
spring.rabbitmq.template.receive-timeout 设置默认超时时间(可选)
阅读全文 »

初识 Kafka

什么是 Kafka

Kafka 是由 Linkedin 公司开发的,它是一个分布式的,支持多分区、多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订阅模式的消息引擎系统。

Kafka 的基本术语

消息:Kafka 中的数据单元被称为消息,也被称为记录,可以把它看作数据库表中某一行的记录。

批次:为了提高效率, 消息会分批次写入 Kafka,批次就代指的是一组消息。

主题:消息的种类称为 主题(Topic), 可以说一个主题代表了一类消息。相当于是对消息进行分类。主题就像是数据库中的表。

分区:主题可以被分为若干个分区(partition),同一个主题中的分区可以不在一个机器上,有可能会部署在多个机器上,由此来实现 kafka 的伸缩性,单一主题中的分区有序,但是无法保证主题中所有的分区有序

生产者:向主题发布消息的客户端应用程序称为生产者(Producer),生产者用于持续不断的向某个主题发送消息。

消费者:订阅主题消息的客户端程序称为消费者(Consumer),消费者用于处理生产者产生的消息。

消费者群组:生产者与消费者的关系就如同餐厅中的厨师和顾客之间的关系一样,一个厨师对应多个顾客,也就是一个生产者对应多个消费者,消费者群组(Consumer Group)指的就是由一个或多个消费者组成的群体。

偏移量:偏移量(Consumer Offset)是一种元数据,它是一个不断递增的整数值,用来记录消费者发生重平衡时的位置,以便用来恢复数据。

broker: 一个独立的 Kafka 服务器就被称为 broker,broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。

broker集群:broker 是集群 的组成部分,broker 集群由一个或多个 broker 组成,每个集群都有一个 broker 同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。

副本:Kafka 中消息的备份又叫做 副本(Replica),副本的数量是可以配置的,Kafka 定义了两类副本:领导者副本(Leader Replica) 和 追随者副本(Follower Replica),前者对外提供服务,后者只是被动跟随。

重平衡:Rebalance。消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。

阅读全文 »

前言

SQL注入是Web安全界地位很高的一个漏洞,它把矛头对准网站最为重要的数据库,利用成本低而危害巨大。若说一个普通开发人员有那么点儿安全意识,那一定和SQL注入有关。如今随着Web安全逐渐被重视,大家安全意识提升,同时各种预编译框架、ORM层出不穷,SQL注入已不像10年前那么泛滥,那么SQL注入的前世今生是怎样的?在这个预编译时代,SQL注入为何仍未销声匿迹?而预编译的底层又有哪些细节需要安全工程师知晓?这将是本文所重点探究之处。

阅读全文 »

需求:统计表中连续超过3天都提交了任务的用户名

SELECT rn AS '连续提交任务天数', submit_creator AS '提交人' FROM
(
    SELECT
        @rn:= CASE
            -- 当前记录的用户名等于上一条记录的用户名 AND 当前记录的日期与上一条记录的日期之差为1
            WHEN @pre_submit_creator=creator AND DATEDIFF(@pre_submit_date,submit_date) = 1
            THEN @rn + 1
            ELSE 1 END AS rn,
        @pre_submit_date:=submit_date AS submit_date,
        @pre_submit_creator:=creator AS submit_creator
    FROM
        (
            -- 日期降序并去重
            SELECT DISTINCT DATE(gmt_create) AS submit_date, creator FROM task_log
            ORDER BY DATE(gmt_create) DESC
        )    AS log,
    (SELECT @rn=0, @submit_creator=null, @submit_date=null) AS b
) log
where rn >= 3;