实战Spring Boot 2.0系列(五) - Listener, Servlet, Filter和Interceptor

11,899 阅读15分钟

前言

用户认证授权、日志记录 MDC、编码解码、UA 检查、多端对应等都需要通过 拦截请求 来进行处理。这时就需要 ServletFilterListenerInterceptor 这几种组件。而把非 Spring Boot 项目转换成 Spring Boot 项目,需要沿用以前的这些代码,所以有必要了解这它们的 用法生命周期

本系列文章

  1. 实战Spring Boot 2.0系列(一) - 使用Gradle构建Docker镜像
  2. 实战Spring Boot 2.0系列(二) - 全局异常处理和测试
  3. 实战Spring Boot 2.0系列(三) - 使用@Async进行异步调用详解
  4. 实战Spring Boot 2.0系列(四) - 使用WebAsyncTask处理异步任务
  5. 实战Spring Boot 2.0系列(五) - Listener, Servlet, Filter和Interceptor
  6. 实战Spring Boot 2.0系列(六) - 单机定时任务的几种实现

正文

1. 几种组件介绍

1.1. 监听器Listener

Listener 可以监听 web 服务器中某一个 事件操作,并触发注册的 回调函数。通俗的语言就是在 applicationsessionrequest 三个对象 创建/消亡 或者 增删改 属性时,自动执行代码的功能组件。

1.2. Servlet

Servlet 是一种运行 服务器端java 应用程序,具有 独立于平台和协议 的特性,并且可以动态的生成 web 页面,它工作在 客户端请求服务器响应 的中间层。

1.3. 过滤器Filter

Filter用户请求 进行 预处理,接着将请求交给 Servlet 进行 处理生成响应,最后 Filter 再对 服务器响应 进行 后处理Filter 是可以复用的代码片段,常用来转换 HTTP 请求响应头信息Filter 不像 Servlet,它不能产生 响应,而是只 修改 对某一资源的 请求 或者 响应

1.4. 拦截器Interceptor

类似 面向切面编程 中的 切面通知,我们通过 动态代理 对一个 service() 方法添加 通知 进行功能增强。比如说在方法执行前进行 初始化处理,在方法执行后进行 后置处理拦截器 的思想和 AOP 类似,区别就是 拦截器 只能对 ControllerHTTP 请求进行拦截。

2. 过滤器 VS 拦截器

2.1. 两者的区别

  1. Filter 是基于 函数回调的,而 Interceptor 则是基于 Java 反射动态代理

  2. Filter 依赖于 Servlet 容器,而 Interceptor 不依赖于 Servlet 容器。

  3. Filter 对几乎 所有的请求 起作用,而 Interceptor 只对 Controller 对请求起作用。

2.2. 执行顺序

对于自定义 Servlet 对请求分发流程:

  1. Filter 过滤请求处理;
  2. Servlet 处理请求;
  3. Filter 过滤响应处理。

对于自定义 Controller 的请求分发流程:

  1. Filter 过滤请求处理;
  2. Interceptor 拦截请求处理;
  3. 对应的 HandlerAdapter 处理请求;
  4. Interceptor 拦截响应处理;
  5. Interceptor 的最终处理;
  6. Filter 过滤响应处理。

3. 环境准备

配置gradle依赖

利用 Spring Initializer 创建一个 gradle 项目 spring-boot-listener-servlet-filter-interceptor,创建时添加相关依赖。得到的初始 build.gradle 如下:

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'io.ostenant.springboot.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

配置启动入口类

配置一个 Spring Boot 启动入口类,这里需要配置两个注解。

  • @ServletComponentScan: 允许 Spring Boot 扫描和装载当前 包路径子路径 下配置的 Servlet

  • @EnableWvc: 允许 Spring Boot 配置 Spring MVC 相关自定义的属性,比如:拦截器、资源处理器、消息转换器等。

@EnableWebMvc
@ServletComponentScan
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. 配置监听器Listener

配置一个 ServletContext 监听器,使用 @WebListener 标示即可。在 Servlet 容器 初始化 过程中,contextInitialized() 方法会被调用,在容器 销毁 时会调用 contextDestroyed()

@WebListener
public class IndexServletContextListener implements ServletContextListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(IndexServletContextListener.class);
    public static final String INITIAL_CONTENT = "Content created in servlet Context";

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        LOGGER.info("Start to initialize servlet context");
        ServletContext servletContext = sce.getServletContext();
        servletContext.setAttribute("content", INITIAL_CONTENT);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        LOGGER.info("Destroy servlet context");
    }
}

这里在容器初始化时,往 ServletContext 上下文设置了参数名称为 INITIAL_CONTENT,可以全局直接访问。

5. 配置Servlet

配置 IndexHttpServlet,重写 HttpServletdoGet() 方法,直接输出 IndexHttpServlet 定义的 初始化参数 和在 IndexServletContextListener 设置的 ServletContext 上下文参数。

@WebServlet(name = "IndexHttpServlet",
        displayName = "indexHttpServlet",
        urlPatterns = {"/index/IndexHttpServlet"},
        initParams = {
                @WebInitParam(name = "createdBy", value = "Icarus"),
                @WebInitParam(name = "createdOn", value = "2018-06-20")
        }
)
public class IndexHttpServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        resp.getWriter().println(format("Created by %s", getInitParameter("createdBy")));
        resp.getWriter().println(format("Created on %s", getInitParameter("createdOn")));
        resp.getWriter().println(format("Servlet context param: %s",
                req.getServletContext().getAttribute("content")));
    }
}

配置 @WebServlet 注解用于注册这个 Servlet@WebServlet 注解的 各个参数 分别对应 web.xml 中的配置:

<servlet-mapping>  
    <servlet-name>IndexHttpServlet</servlet-name>
    <url-pattern>/index/IndexHttpServlet</url-pattern>
</servlet-mapping>
<servlet>  
    <servlet-name>IndexHttpServlet</servlet-name>  
    <servlet-class>io.ostenant.springboot.sample.servlet.IndexHttpServlet</servlet-class>
    <init-param>
        <param-name>createdBy</param-name>
        <param-value>Icarus</param-value>
    </init-param>
    <init-param>
        <param-name>createdOn</param-name>
        <param-value>2018-06-20</param-value>
    </init-param>
</servlet>  

6. 配置过滤器Filter

一个 Servlet 请求可以经由多个 Filter 进行过滤,最终由 Servlet 处理并响应客户端。这里配置两个过滤器示例:

FirstIndexFilter.java

@WebFilter(filterName = "firstIndexFilter",
        displayName = "firstIndexFilter",
        urlPatterns = {"/index/*"},
        initParams = @WebInitParam(
                name = "firstIndexFilterInitParam",
                value = "io.ostenant.springboot.sample.filter.FirstIndexFilter")
)
public class FirstIndexFilter implements Filter {
    private static final Logger LOGGER = LoggerFactory.getLogger(FirstIndexFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LOGGER.info("Register a new filter {}", filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        LOGGER.info("FirstIndexFilter pre filter the request");
        String filter = request.getParameter("filter1");
        if (isEmpty(filter)) {
            response.getWriter().println("Filtered by firstIndexFilter, " +
                    "please set request parameter \"filter1\"");
            return;
        }
        chain.doFilter(request, response);
        LOGGER.info("FirstIndexFilter post filter the response");
    }

    @Override
    public void destroy() {
        LOGGER.info("Destroy filter {}", getClass().getName());
    }
}

以上 @WebFilter 相关的配置属性,对应于 web.xml 的配置如下:

<filter-mapping>
    <filter-name>firstIndexFilter</filter-name>
    <filter-class>io.ostenant.springboot.sample.filter.FirstIndexFilter</filter-class>
    <url-pattern>/index/*</url-pattern>
    <init-param>
        <param-name>firstIndexFilterInitParam</param-name>
        <param-value>io.ostenant.springboot.sample.filter.FirstIndexFilter</param-value>
    </init-param>
</filter-mapping>

配置 FirstIndexFilter,使用 @WebFilter 注解进行标示。当 FirstIndexFilter 初始化时,会执行 init() 方法。每次请求路径匹配 urlPatterns 配置的路径时,就会进入 doFilter() 方法进行具体的 请求响应过滤

HTTP 请求携带 filter1 参数时,请求会被放行;否则,直接 过滤中断,结束请求处理。

SecondIndexFilter.java

@WebFilter(filterName = "secondIndexFilter",
        displayName = "secondIndexFilter",
        urlPatterns = {"/index/*"},
        initParams = @WebInitParam(
                name = "secondIndexFilterInitParam",
                value = "io.ostenant.springboot.sample.filter.SecondIndexFilter")
)
public class SecondIndexFilter implements Filter {
    private static final Logger LOGGER = LoggerFactory.getLogger(SecondIndexFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LOGGER.info("Register a new filter {}", filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        LOGGER.info("SecondIndexFilter pre filter the request");
        String filter = request.getParameter("filter2");
        if (isEmpty(filter)) {
            response.getWriter().println("Filtered by firstIndexFilter, " +
                    "please set request parameter \"filter2\"");
            return;
        }
        chain.doFilter(request, response);
        LOGGER.info("SecondIndexFilter post filter the response");

    }

    @Override
    public void destroy() {
        LOGGER.info("Destroy filter {}", getClass().getName());
    }
}

以上 @WebFilter 相关的配置属性,对应于 web.xml 的配置如下:

<filter-mapping>
    <filter-name>secondIndexFilter</filter-name>
    <filter-class>io.ostenant.springboot.sample.filter.SecondIndexFilter</filter-class>
    <url-pattern>/index/*</url-pattern>
    <init-param>
        <param-name>secondIndexFilterInitParam</param-name>
        <param-value>io.ostenant.springboot.sample.filter.SecondIndexFilter</param-value>
    </init-param>
</filter-mapping>

配置 SecondIndexFilter,使用 @WebFilter 注解进行标示。当 SecondIndexFilter 初始化时,会执行 init() 方法。每次请求路径匹配 urlPatterns 配置的路径时,就会进入 doFilter() 方法进行具体的 请求响应过滤

HTTP 请求携带 filter2 参数时,请求会被放行;否则,直接 过滤中断,结束请求处理。

来看看 doFilter() 最核心的三个参数:

  • ServletRequest: 未到达 ServletHTTP 请求;
  • ServletResponse: 由 Servlet 处理并生成的 HTTP 响应;
  • FilterChain: 过滤器链 对象,可以按顺序注册多个 过滤器
FilterChain.doFilter(request, response);

解释: 一个 过滤器链 对象可以按顺序注册多个 过滤器。符合当前过滤器过滤条件,即请求 过滤成功 直接放行,则交由下一个 过滤器 进行处理。所有请求过滤完成以后,由 IndexHttpServlet 处理并生成 响应,然后在 过滤器链 以相反的方向对 响应 进行后置过滤处理。

配置控制器Controller

配置 IndexController,用于测试 /index/IndexController 路径是否会被 Filter 过滤和 Interceptor 拦截,并验证两者的先后顺序。

@RestController
@RequestMapping("index")
public class IndexController {
    @GetMapping("IndexController")
    public String index() throws Exception {
        return "IndexController";
    }
}

7. 配置拦截器Interceptor

拦截器 Interceptor 只对 Handler 生效。Spring MVC 会为 Controller 中的每个 请求方法 实例化为一个 Handler对象,由 HandlerMapping 对象路由请求到具体的 Handler,然后由 HandlerAdapter 通过反射进行请求 处理响应,这中间就穿插着 拦截处理

编写拦截器

为了区分日志,下面同样对 IndexController 配置两个拦截器类:

FirstIndexInterceptor.java

public class FirstIndexInterceptor implements HandlerInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(FirstIndexInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        LOGGER.info("FirstIndexInterceptor pre intercepted the request");
        String interceptor = request.getParameter("interceptor1");
        if (isEmpty(interceptor)) {
            response.getWriter().println("Filtered by FirstIndexFilter, " +
                    "please set request parameter \"interceptor1\"");
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        LOGGER.info("FirstIndexInterceptor post intercepted the response");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LOGGER.info("FirstIndexInterceptor do something after request completed");
    }
}

SecondIndexInterceptor.java

public class SecondIndexInterceptor implements HandlerInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(SecondIndexInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        LOGGER.info("SecondIndexInterceptor pre intercepted the request");
        String interceptor = request.getParameter("interceptor2");
        if (isEmpty(interceptor)) {
            response.getWriter().println("Filtered by SecondIndexInterceptor, " +
                    "please set request parameter \"interceptor2\"");
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        LOGGER.info("SecondIndexInterceptor post intercepted the response");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LOGGER.info("SecondIndexInterceptor do something after request completed");
    }
}

配置拦截器

Spring Boot配置拦截器 很简单,只需要实现 WebMvcConfigurer 接口,在 addInterceptors() 方法中通过 InterceptorRegistry 添加 拦截器匹配路径 即可。

@Configuration
public class WebConfiguration implements WebMvcConfigurer {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebConfiguration.class);

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new FirstIndexInterceptor()).addPathPatterns("/index/**");
        registry.addInterceptor(new SecondIndexInterceptor()).addPathPatterns("/index/**");
        LOGGER.info("Register FirstIndexInterceptor and SecondIndexInterceptor onto InterceptorRegistry");
    }
}

对应的 Spring XML 配置方式如下:

<bean id="firstIndexInterceptor"
class="io.ostenant.springboot.sample.interceptor.FirstIndexInterceptor"></bean>
<bean id="secondIndexInterceptor"
class="io.ostenant.springboot.sample.interceptor.SecondIndexInterceptor"></bean>

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/index/**" />
        <ref local="firstIndexInterceptor" />
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/index/**" />
        <ref local="secondIndexInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

原理剖析

我们通过实现 HandlerInterceptor 接口来开发一个 拦截器,来看看 HandlerInterceptor 接口的三个重要的方法:

  • preHandle(): 在 controller 接收请求、处理 request 之前执行,返回值为 boolean,返回值为 true 时接着执行 postHandle()afterCompletion() 方法;如果返回 false中断 执行。

  • postHandle(): 在 controller 处理请求之后, ModelAndView 处理前执行,可以对 响应结果 进行修改。

  • afterCompletion(): 在 DispatchServlet 对本次请求处理完成,即生成 ModelAndView 之后执行。

下面简单的看一下 Spring MVC 中心调度器 DispatcherServletdoDispatch() 方法的原理,重点关注 拦截器 的以上三个方法的执行顺序。

  • doDispatch(): DispatchServlet 处理请求分发的核心方法。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 1. 按从前往后的顺序调用各个拦截器preHandle()方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 2. HandlerAdapter开始真正的请求处理并生产响应视图对象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);

            // 3. 按照从后往前的顺序依次调用各个拦截器的postHandle()方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        } catch (Exception ex) {
            dispatchException = ex;
        } catch (Throwable err) {
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
        // 4. 最终会调用拦截器的afterCompletion()方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    } catch (Throwable err) {
        // 4. 最终会调用拦截器的afterCompletion()方法
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else {
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

上面注释的几个 HandlerExecutionChain 的方法: applyPreHandle()applyPostHandle()triggerAfterCompletion()

  • applyPreHandle(): 按 从前往后 的顺序调用各个拦截器的 preHandle() 方法。任意一个 HandlerInterceptor 拦截返回 false ,则 preHandle() 返回 false,记录拦截器的位置 interceptorIndex,然后中断拦截处理,最终触发 AfterCompletion() 方法并返回 false
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}
  • applyPostHandle(): 按照 从后往前 的顺序依次调用各个拦截器的 postHandle() 方法。只有当所有 HandlerInterceptorpreHandle() 方法返回 true 时,才有机会执行到 applyPostHandle() 方法。
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
        throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}
  • triggerAfterCompletion: triggerAfterCompletion() 只在 preHandle() 方法返回 false程序抛出异常 时执行。在 preHandle() 方法中,通过 interceptorIndex 记录了返回 false拦截器索引。一旦 applyPreHandle() 方法返回 false,则从当前返回 false 的拦截器 从后往前 的执行 afterCompletion() 方法。
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
        throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

8. 开始测试

生命周期测试

启动 Spring Boot 应用程序,观察启动时的程序日志,下面我按照 顺序 来分析启动过程中完成了哪些事情。

  • 注册 Spring MVCdispatcherServlet 和自定义的 IndexHttpServlet
2018-06-23 09:39:55.400  INFO 12301 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-06-23 09:39:55.404  INFO 12301 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet IndexHttpServlet mapped to [/index/IndexHttpServlet]

注意: dispatcherServletload-up-onstartup1,会优先于其他 Servlet 进行加载。

  • 按照先后顺序,将所有的过滤器 Filter 对象与路径进行映射,其中 characterEncodingFilterSpring MVC 自带的解决乱码的 Filter
2018-06-23 09:39:55.408  INFO 12301 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-06-23 09:39:55.409  INFO 12301 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'firstIndexFilter' to urls: [/index/*]
2018-06-23 09:39:55.409  INFO 12301 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'secondIndexFilter' to urls: [/index/*]
  • 初始化 IndexServletContextListener,并执行 contextInitialized() 方法进行上下文初始化操作。
2018-06-23 09:39:55.429  INFO 12301 --- [ost-startStop-1] i.o.s.s.l.IndexServletContextListener    : Start to initialize servlet context
  • 依次执行 Filterinit() 方法进行初始化处理。
2018-06-23 09:39:55.432  INFO 12301 --- [ost-startStop-1] i.o.s.sample.filter.SecondIndexFilter     : Register a new filter secondIndexFilter
2018-06-23 09:39:55.434  INFO 12301 --- [ost-startStop-1] i.o.s.sample.filter.FirstIndexFilter      : Register a new filter firstIndexFilter
  • 创建、初始化拦截器,并统一注册到 InterceptorRegistry 上。
2018-06-23 09:39:55.502  INFO 13150 --- [           main] i.o.s.s.interceptor.WebConfiguration     : Register FirstIndexInterceptor and SecondIndexInterceptor onto InterceptorRegistry
  • IndexController 进行处理,把 请求 URI处理方法 映射到 HandlerMapping 上并进行缓存。
2018-06-23 09:39:55.541  INFO 12301 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/index/IndexController],methods=[GET]}" onto public java.lang.String io.ostenant.springboot.sample.controller.IndexController.index() throws java.lang.Exception

关闭 Spring Boot 应用程序时,观察输出日志如下:

2018-06-23 10:07:03.294  INFO 12301 --- [ost-startStop-2] i.o.s.sample.filter.FirstIndexFilter     : Destroy filter io.ostenant.springboot.sample.filter.SecondIndexFilter
2018-06-23 10:07:03.294  INFO 12301 --- [ost-startStop-2] i.o.s.sample.filter.FirstIndexFilter     : Destroy filter io.ostenant.springboot.sample.filter.FirstIndexFilter
2018-06-23 10:07:03.294  INFO 12301 --- [ost-startStop-2] i.o.s.s.l.IndexServletContextListener    : Destroy servlet context

可以看到上面配置的过滤器的 destroy() 方法和 IndexServletContextListenercontextDestroyed() 方法都被调用了。

访问控制测试

Servlet测试

访问 http://localhost:8080/index/IndexHttpServlet,响应页面内容如下:

访问 http://localhost:8080/index/IndexHttpServlet?filter1=filter1,响应页面内容如下:

访问 http://localhost:8080/index/IndexHttpServlet?filter1=filter1&filter2=filter2,响应页面内容如下:

观察控制台输出日志,验证 过滤器 的过滤顺序正确。

2018-06-23 10:19:47.944  INFO 13150 --- [nio-8080-exec-1] i.o.s.sample.filter.FirstIndexFilter     : FirstIndexFilter pre filter the request
2018-06-23 10:19:47.944  INFO 13150 --- [nio-8080-exec-1] i.o.s.sample.filter.SecondIndexFilter    : SecondIndexFilter pre filter the request
2018-06-23 10:19:47.944  INFO 13150 --- [nio-8080-exec-1] i.o.s.sample.filter.SecondIndexFilter    : SecondIndexFilter post filter the response
2018-06-23 10:19:47.944  INFO 13150 --- [nio-8080-exec-1] i.o.s.sample.filter.FirstIndexFilter     : FirstIndexFilter post filter the response

结论: 自定义的 过滤器IndexHttpServlet 生效, 而 自定义 的拦截器生效。

controller测试

访问 http://localhost:8080/index/IndexController,响应页面内容如下:

访问 http://localhost:8080/index/IndexController?filter1=filter1,响应页面内容如下:

访问 http://localhost:8080/index/IndexController?filter1=filter1&filter2=filter2,响应页面内容如下:

访问 http://localhost:8080/index/IndexController?filter1=filter1&filter2=filter2&interceptor1=interceptor1,响应页面内容如下:

访问 http://localhost:8080/index/IndexController?filter1=filter1&filter2=filter2&interceptor1=interceptor1&interceptor2=interceptor2,响应页面内容如下:

2018-06-23 10:21:42.533  INFO 13150 --- [nio-8080-exec-4] i.o.s.sample.filter.FirstIndexFilter     : FirstIndexFilter pre filter the request
2018-06-23 10:21:42.533  INFO 13150 --- [nio-8080-exec-4] i.o.s.sample.filter.SecondIndexFilter    : SecondIndexFilter pre filter the request
2018-06-23 10:21:42.534  INFO 13150 --- [nio-8080-exec-4] i.o.s.s.i.FirstIndexInterceptor          : FirstIndexInterceptor pre intercepted the request
2018-06-23 10:21:42.534  INFO 13150 --- [nio-8080-exec-4] i.o.s.s.i.SecondIndexInterceptor         : SecondIndexInterceptor pre intercepted the request
2018-06-23 10:21:42.535  INFO 13150 --- [nio-8080-exec-4] i.o.s.s.i.SecondIndexInterceptor         : SecondIndexInterceptor post intercepted the response
2018-06-23 10:21:42.535  INFO 13150 --- [nio-8080-exec-4] i.o.s.s.i.FirstIndexInterceptor          : FirstIndexInterceptor post intercepted the response
2018-06-23 10:21:42.535  INFO 13150 --- [nio-8080-exec-4] i.o.s.s.i.SecondIndexInterceptor         : SecondIndexInterceptor do something after request completed
2018-06-23 10:21:42.535  INFO 13150 --- [nio-8080-exec-4] i.o.s.s.i.FirstIndexInterceptor          : FirstIndexInterceptor do something after request completed
2018-06-23 10:21:42.535  INFO 13150 --- [nio-8080-exec-4] i.o.s.sample.filter.SecondIndexFilter    : SecondIndexFilter post filter the response
2018-06-23 10:21:42.535  INFO 13150 --- [nio-8080-exec-4] i.o.s.sample.filter.FirstIndexFilter     : FirstIndexFilter post filter the response

结论: 自定义的 过滤器拦截器控制器 Controller 生效。而 过滤器 的优先级高于 拦截器

小结

本文详细介绍了 ListenerServletFilterControllerInterceptorWeb 多种组件的功能、方法、顺序、作用域和生命周期。给出了详细的示例代码,结合 源码 分析了流程,结合 测试 验证了结论。长篇大论,希望大家对 Servlet 组件和 Spring MVC 的框架组件有了更清晰的认识。


欢迎关注技术公众号: 零壹技术栈

零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。