为了账号安全,请及时绑定邮箱和手机立即绑定

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

前言

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

正文

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-web-async-task创建时添加相关依赖。得到的初始 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 = "Vainlgory"),
                @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>Vainlgory</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 拦截返回 falsepreHandle() 返回 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响应页面内容如下

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

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响应页面内容如下

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 的框架组件有了更清晰的认识。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消