SpringBoot整合JavaWeb三大組件(Servlet、Listener、Filter)

幾種組件介紹

監聽器Listener

Listener 可以監聽 web 服務器中某一個 事件操作並觸發註冊的 回調函數。通俗的語言就是在 application``session``request 三個對象 創建/消亡 或者 增刪改 屬性時自動執行代碼的功能組件。

Servlet

Servlet 是一種運行 服務器端java 應用程序具有 獨立於平臺和協議 的特性並且可以動態的生成 web 頁面它工作在 客戶端請求服務器響應 的中間層。

過濾器Filter

Filter用戶請求 進行 預處理接着將請求交給 Servlet 進行 處理生成響應最後 Filter 再對 服務器響應 進行 後處理Filter 是可以複用的代碼片段常用來轉換 HTTP 請求響應頭信息Filter 不像 Servlet它不能產生 響應而是隻 修改 對某一資源的 請求 或者 響應

攔截器Interceptor

類似 面向切面編程 中的 切面通知我們通過 動態代理 對一個 service() 方法添加 通知 進行功能增強。比如說在方法執行前進行 初始化處理在方法執行後進行 後置處理攔截器 的思想和 AOP 類似區別就是 攔截器 只能對 ControllerHTTP 請求進行攔截。

 

過濾器 VS 攔截器

兩者的區別

  1. Filter 是基於 函數回調的而 Interceptor 則是基於 Java 反射動態代理
  2. Filter 依賴於 Servlet 容器而 Interceptor 不依賴於 Servlet 容器。
  3. Filter 對幾乎 所有的請求 起作用而 Interceptor 只對 Controller 對請求起作用。

執行順序

對於自定義 Servlet 對請求分發流程

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

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

  1. Filter 過濾請求處理
  2. Interceptor 攔截請求處理
  3. 對應的 HandlerAdapter 處理請求
  4. Interceptor 攔截響應處理
  5. Interceptor 的最終處理
  6. Filter 過濾響應處理。

攔截器(Interceptor)和過濾器(Filter)的區別

 

代碼實現

配置啓動入口類

配置一個 Spring Boot 啓動入口類這裏需要配置兩個註解。

  • @ServletComponentScan: 允許 Spring Boot 掃描和裝載當前 包路徑子路徑 下配置的 Servlet
  • @EnableMvc: 允許 Spring Boot 配置 Spring MVC 相關自定義的屬性比如攔截器、資源處理器、消息轉換器等。
@EnableWebMvc
@ServletComponentScan
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

配置監聽器Listener

配置一個 ServletContext 監聽器使用 @WebListener 標示即可。在 Servlet 容器 初始化 過程中contextInitialized() 方法會被調用在容器 銷燬 時會調用 contextDestroyed()

使用WebListener註解

@WebListener
public class ContextListener implements ServletContextListener {
    @Autowired
    private RedisClient redisClient;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext initialized");

        // 在容器初始化時往 ServletContext 上下文設置了參數名稱爲 CtxPath 可以全局直接訪問
        sce.getServletContext().setAttribute("CtxPath", "/jaemon");
        redisClient.setKV("applicationContext", "/jaemon");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext destroyed");
    }
}

不用WebListener註解

@Bean
public ServletListenerRegistrationBean<ContextListener> getServletListenerRegistrationBean() {
    ServletListenerRegistrationBean<ContextListener> bean = new ServletListenerRegistrationBean<>(
        new ContextListener());
    return bean;
}

配置Servlet

配置 IndexHttpServlet重寫 HttpServletdoGet() 方法直接輸出 IndexHttpServlet 定義的 初始化參數 和在 IndexServletContextListener 設置的 ServletContext 上下文參數。

使用WebServlet註解

@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註解

public class IndexHttpServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        // ...
    }
}
@Bean
public ServletRegistrationBean getServletRegistrationBean(){
   ServletRegistrationBean bean = new ServletRegistrationBean(new IndexHttpServlet());
   bean.addUrlMappings("/index/IndexHttpServlet");
   return bean;
}

配置 @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>

配置過濾器Filter

一個 Servlet 請求可以經由多個 Filter 進行過濾最終由 Servlet 處理並響應客戶端。這裏配置兩個過濾器示例

使用WebFilter註解

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註解

@Bean
public FilterRegistrationBean getFilterRegistrationBean() {
    FilterRegistrationBean bean = new FilterRegistrationBean(new FirstIndexFilter());
    // bean.addUrlPatterns(new String[]{"*.do","*.jsp"});//攔截多個時
    bean.addUrlPatterns("/index/*");
    return bean;
}

以上 @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";
    }
}

配置攔截器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 之後執行。

攔截器在DispatcherServlet中的應用

下面簡單的看一下 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);
            }
        }
    }
}

 

Reference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章