過濾器 Filter,是在 Servlet 規範中定義的,是 Servlet 容器支持的,該接口定義在 javax.servlet
包下,主要是在客戶端請求(HttpServletRequest)進行預處理,以及對服務器響應(HttpServletResponse)進行後處理。接口代碼以下:java
package javax.servlet;
import java.io.IOException;
public interface Filter {
void init(FilterConfig var1) throws ServletException;
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
void destroy();
}
複製代碼
對上面三個接口方法進行分析:web
FilterChain.doFilter
能夠將請求繼續傳遞下去,若是想攔截這個請求,能夠不調用 FilterChain.doFilter,那麼這個請求就直接返回了,因此 Filter 是一種責任鏈設計模式,在spring security
就大量使用了過濾器,有一條過濾器鏈。首先咱們須要建立一個類,讓它實現 Filter 接口,而後重寫接口中的方法:spring
package com.example.demojava.filter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
@Order(1) // 過濾順序,值越小越先執行
@WebFilter(urlPatterns = "/demoFilter", filterName = "filterTest")
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter初始化中...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter()開始執行:發往 " + ((HttpServletRequest) servletRequest).getRequestURL().toString() + " 的請求已被攔截");
System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());
// filter的鏈式調用;將請求轉給下一條過濾鏈
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());
System.out.println("doFilter()執行結束。");
}
@Override
public void destroy() {
System.out.println("filter銷燬中...");
}
}
複製代碼
當咱們配置了多個 filter,且一個請求可以被屢次攔截時,該請求將沿着 客戶端 -> 過濾器1 -> 過濾器2 -> servlet -> 過濾器2 -> 過濾器1 -> 客戶端
鏈式流轉設計模式
@Component
@Order(2) // 過濾順序,值越小越先執行
@WebFilter(urlPatterns = "/demoFilter", filterName = "filterTest2")
public class Filter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter2初始化中...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter2()開始執行:發往 " + ((HttpServletRequest) servletRequest).getRequestURL().toString() + " 的請求已被攔截");
System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());
// filter的鏈式調用;將請求轉給下一條過濾鏈
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());
System.out.println("doFilter2()執行結束。");
}
@Override
public void destroy() {
System.out.println("filter2銷燬中...");
}
}
複製代碼
而後建立一個 Controller,對外提供兩條請求路徑:tomcat
@RestController
@RequestMapping("demoFilter")
public class FilterController {
@GetMapping("hello")
public String hello() {
System.out.println("接口被調用:hello() ");
return "hello filter";
}
@GetMapping("hi")
public String hi() {
System.out.println("接口被調用:hi()");
return "hi filter";
}
}
複製代碼
啓動項目,能夠看到咱們的過濾器已經隨着程序的啓動被成功初始化了。bash
分別對這兩個接口發送請求, 看到結果:服務器
doFilter()開始執行:發往 http://localhost:8080/demoFilter/hi 的請求已被攔截
檢驗接口是否被調用,嘗試獲取contentType以下: null
doFilter2()開始執行:發往 http://localhost:8080/demoFilter/hi 的請求已被攔截
檢驗接口是否被調用,嘗試獲取contentType以下: null
接口被調用:hi()
檢驗接口是否被調用,嘗試獲取contentType以下: text/plain;charset=UTF-8
doFilter2()執行結束。
檢驗接口是否被調用,嘗試獲取contentType以下: text/plain;charset=UTF-8
doFilter()執行結束。
複製代碼
最後使項目中止運行,則過濾器隨之銷燬。cookie
能夠看出,當請求同時知足多個過濾器的過濾條件時,filterChain.doFilter()
會將其按必定順序(能夠經過 @Order 指定)依次傳遞到下一個 filter,直到進入 servlet 進行接口的實際調用。調用完成後,響應結果將沿着原路返回,並在再一次通過各個 filter 後,最終抵達客戶端。app
攔截器是 AOP 的一種實現策略,用於在某個方法或字段被訪問前對它進行攔截,而後在其以前或以後加上某些操做。同 filter 同樣,interceptor 也是鏈式調用。每一個 interceptor 的調用會依據它的聲明順序依次執行。通常來講攔截器能夠用於如下方面 :框架
日誌記錄 :概率請求信息的日誌,以便進行信息監控、信息統計等等
權限檢查 :對用戶的訪問權限,認證,或受權等進行檢查
性能監控 :經過攔截器在進入處理器先後分別記錄開始時間和結束時間,從而獲得請求的處理時間
通用行爲 :讀取 cookie 獲得用戶信息並將用戶對象放入請求頭中,從而方便後續流程使用
在 SpringMVC 中,DispatcherServlet 捕獲每一個請求,在到達對應的 Controller 以前,請求能夠被攔截器處理,在攔截器中進行前置處理後,請求最終纔到達 Controller。
攔截器的接口是 org.springframework.web.servlet.HandlerInterceptor
接口,接口代碼以下:
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
複製代碼
接口方法解讀:
preHandle
方法返回 true。具體來講,postHandler
方法會在 DispatcherServlet 進行視圖返回渲染前被調用,也就是說咱們能夠在這個方法中對 Controller 處理以後的ModelAndView
對象進行操做preHandle
方法的返回值爲 true 才行。該方法通常用於資源清理工做一樣,首先建立一個類,讓它實現 HandlerInterceptor 接口,而後重寫接口中的方法 :
package com.demo.demofilter.demofilter.interceptor;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class DemoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("afterHandle");
}
}
複製代碼
複製代碼
緊接着須要對攔截器進行註冊,指明使用哪一個攔截器,及該攔截器對應攔截的 URL :
package com.demo.demofilter.demofilter.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 若是有多個攔截器,繼續registry.add往下添加就能夠啦
registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/demoInterceptor/**");
}
}
複製代碼
複製代碼
最後是 Controller
package com.demo.demofilter.demofilter.interceptor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("demoInterceptor")
public class InterceptorController {
@GetMapping("hello")
public String hello() {
System.out.println("接口被調用:hello() ");
return "hello interceptor";
}
@GetMapping("hi")
public String hi() {
System.out.println("接口被調用:hi()");
return "hi interceptor";
}
}
複製代碼
複製代碼
運行結果以下 :
preHandler
接口被調用: hello()
postHandler
afterHandler
複製代碼
在 Http 的請求執行過程當中,要通過如下幾個步驟 :
因此,只有通過 DispatcherServlet 的請求才會被攔截器捕獲,而咱們自定義的 Servlet 請求則不會被攔截的。
preHandle
方法內返回 false 進行中斷。過濾器就比較複雜,須要處理請求和響應對象來引起中斷,須要額外的動做,好比將用戶重定向到錯誤頁面tomcat 容器中執行順序: Filter -> Servlet -> Interceptor -> Controller
本文到此結束,感謝閱讀。若是您以爲不錯,請關注公衆號【當我趕上你】,您的支持是我寫做的最大動力。