實戰Spring Boot 2.0系列(五) - Listener, Servlet, Filter和Interceptor

前言

用戶認證受權、日誌記錄 MDC、編碼解碼、UA 檢查、多端對應等都須要經過 攔截請求 來進行處理。這時就須要 ServletFilterListenerInterceptor 這幾種組件。而把非 Spring Boot 項目轉換成 Spring Boot 項目,須要沿用之前的這些代碼,因此有必要了解這它們的 用法生命週期java

本系列文章

  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 三個對象 建立/消亡 或者 增刪改 屬性時,自動執行代碼的功能組件。web

1.2. Servlet

Servlet 是一種運行 服務器端java 應用程序,具備 獨立於平臺和協議 的特性,而且能夠動態的生成 web 頁面,它工做在 客戶端請求服務器響應 的中間層。spring

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 容器。springboot

  3. Filter 對幾乎 全部的請求 起做用,而 Interceptor 只對 Controller 對請求起做用。bash

2.2. 執行順序

對於自定義 Servlet 對請求分發流程:服務器

  1. Filter 過濾請求處理;
  2. Servlet 處理請求;
  3. Filter 過濾響應處理。

對於自定義 Controller 的請求分發流程:session

  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 的框架組件有了更清晰的認識。


歡迎關注技術公衆號: 零壹技術棧

零壹技術棧

本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。

相關文章
相關標籤/搜索