前言
使用者認證授權、日誌記錄 MDC
、編碼解碼、UA
檢查、多端對應等都需要通過 攔截請求 來進行處理。這時就需要 Servlet
、Filter
、Listener
、Interceptor
這幾種元件。而把非 Spring Boot
專案轉換成 Spring Boot
專案,需要沿用以前的這些程式碼,所以有必要了解這它們的 用法 和 生命週期。
本系列文章
- 實戰Spring Boot 2.0系列(一) - 使用Gradle構建Docker映象
- 實戰Spring Boot 2.0系列(二) - 全域性異常處理和測試
- 實戰Spring Boot 2.0系列(三) - 使用@Async進行非同步呼叫詳解
- 實戰Spring Boot 2.0系列(四) - 使用WebAsyncTask處理非同步任務
- 實戰Spring Boot 2.0系列(五) - Listener, Servlet, Filter和Interceptor
- 實戰Spring Boot 2.0系列(六) - 單機定時任務的幾種實現
正文
1. 幾種元件介紹
1.1. 監聽器Listener
Listener
可以監聽 web
伺服器中某一個 事件操作,並觸發註冊的 回撥函式。通俗的語言就是在 application
,session
,request
三個物件 建立/消亡 或者 增刪改 屬性時,自動執行程式碼的功能元件。
1.2. Servlet
Servlet
是一種執行 伺服器端 的 java
應用程式,具有 獨立於平臺和協議 的特性,並且可以動態的生成 web
頁面,它工作在 客戶端請求 與 伺服器響應 的中間層。
1.3. 過濾器Filter
Filter
對 使用者請求 進行 預處理,接著將請求交給 Servlet
進行 處理 並 生成響應,最後 Filter
再對 伺服器響應 進行 後處理。Filter
是可以複用的程式碼片段,常用來轉換 HTTP
請求、響應 和 頭資訊。Filter
不像 Servlet
,它不能產生 響應,而是隻 修改 對某一資源的 請求 或者 響應。
1.4. 攔截器Interceptor
類似 面向切面程式設計 中的 切面 和 通知,我們通過 動態代理 對一個 service()
方法新增 通知 進行功能增強。比如說在方法執行前進行 初始化處理,在方法執行後進行 後置處理。攔截器 的思想和 AOP
類似,區別就是 攔截器 只能對 Controller
的 HTTP
請求進行攔截。
2. 過濾器 VS 攔截器
2.1. 兩者的區別
-
Filter
是基於 函式回撥的,而Interceptor
則是基於Java
反射 和 動態代理。 -
Filter
依賴於Servlet
容器,而Interceptor
不依賴於Servlet
容器。 -
Filter
對幾乎 所有的請求 起作用,而Interceptor
只對Controller
對請求起作用。
2.2. 執行順序
對於自定義 Servlet
對請求分發流程:
Filter
過濾請求處理;Servlet
處理請求;Filter
過濾響應處理。
對於自定義 Controller
的請求分發流程:
Filter
過濾請求處理;Interceptor
攔截請求處理;- 對應的
HandlerAdapter
處理請求; Interceptor
攔截響應處理;Interceptor
的最終處理;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
,重寫 HttpServlet
的 doGet()
方法,直接輸出 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: 未到達
Servlet
的HTTP
請求; - 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
中心排程器 DispatcherServlet
的 doDispatch()
方法的原理,重點關注 攔截器 的以上三個方法的執行順序。
- 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()
方法。只有當所有HandlerInterceptor
的preHandle()
方法返回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 MVC
的dispatcherServlet
和自定義的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]
複製程式碼
注意:
dispatcherServlet
的load-up-onstartup
為1
,會優先於其他Servlet
進行載入。
- 按照先後順序,將所有的過濾器
Filter
物件與路徑進行對映,其中characterEncodingFilter
是Spring 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
複製程式碼
- 依次執行
Filter
的init()
方法進行初始化處理。
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()
方法和 IndexServletContextListener
的 contextDestroyed()
方法都被呼叫了。
訪問控制測試
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,響應頁面內容如下:
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
生效。而 過濾器 的優先順序高於 攔截器。
小結
本文詳細介紹了 Listener
,Servlet
,Filter
,Controller
和 Interceptor
等 Web
多種元件的功能、方法、順序、作用域和生命週期。給出了詳細的示例程式碼,結合 原始碼 分析了流程,結合 測試 驗證了結論。長篇大論,希望大家對 Servlet
元件和 Spring MVC
的框架元件有了更清晰的認識。
歡迎關注技術公眾號: 零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。