前言
我們在進行 Web 應用開發時,時常需要對請求進行攔截或處理,故 Spring 為我們提供了過濾器和攔截器來應對這種情況。那麼兩者之間有什麼不同呢?本文將詳細講解兩者的區別和對應的使用場景。
(本文的程式碼實現首先是基於 SpringBoot,Spring 的實現方式僅簡單描述)
1. 過濾器
1.1. 什麼是過濾器
過濾器(Filter),是 Servlet 規範規定的,在 Servlet 前執行的。用於攔截和處理 HTTP 請求和響應,可用於身份認證、授權、日誌記錄和設定字符集(CharacterEncodingFilter)等場景。
過濾器位於整個請求處理流程的最前端,因此在請求到達 Controller 層前,都會先被過濾器處理。
過濾器可以攔截多個請求或響應,一個請求或響應也可以被多個過濾器攔截。
1.2. 如何建立過濾器
Filter 的生命週期對應的三個關鍵方法:
方法 | 說明 |
---|---|
init() | 當請求發起時,會呼叫 init() 方法初始化 Filter 例項,僅初始化一次。若需要設定初始化引數的時可呼叫該方法。 |
doFilter() | 攔截要執行的請求,對請求和響應進行處理。 |
destroy() | 請求結束時呼叫該方法銷燬 Filter 的例項。 |
下面將介紹二種方法建立 Filter。
1.2.1 實現 Filter 介面
1.建立 Filter 處理類,實現javax.servlet.Filter
介面,加上@WebFilter
註解配置攔截 Url,但是不能指定過濾器執行順序,也可透過web.xml
配置。
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 用於完成 Filter 的初始化
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("過濾器已經攔截成功!!!");
// 執行該方法之前,即對使用者請求進行預處理;執行該方法之後,即對伺服器響應進行後處理。
chain.doFilter(request,response);
}
@Override
public void destroy() {
// 用於 Filter 銷燬前,完成某些資源的回收;
Filter.super.destroy();
}
}
2.在啟動類新增註解@ServletComponentScan
,讓 Spring 可以掃描到。
@SpringBootApplication
@ServletComponentScan
public class MyFilterDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MyFilterDemoApplication.class, args);
}
}
3.建立 Controller 發起 Url 請求。
@RestController
public class MyFilterController {
@GetMapping("/testFilter")
public String testFilter(){
return "Hello World";
}
}
攔截結果
1.2.2. 透過@Component 註解
1.建立 Filter 處理類,實現javax.servlet.Filter
介面,加@Component
註解。
- 可以使用
@Order
註解保證過濾器執行順序,不加則按照類名排序。 - 過濾器不能指定攔截的url , 只能預設攔截全部。
@Component
@Order(1)
public class MyComponentFilter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("我是過濾器1已經攔截成功!!!");
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
@Component
@Order(2)
public class MyComponentFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
System.out.println("我是過濾器2已經攔截成功!!!");
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
2-3 步驟同 1.2.1,結果如下。
2. 攔截器
2.1. 什麼是攔截器
攔截器(Interceptor),和Servlet無關,由Spring框架實現。可用於身份認證、授權、日誌記錄、預先設定資料以及統計方法的執行效率等。
一般基於 Java 的反射機制實現,屬於AOP的一種運用。
目前瞭解的 Spring 中的攔截器有:
- HandlerInterceptor
- MethodInterceptor
2.2. HandlerInterceptor 攔截器
2.2.1簡介
HandlerInterceptor 類似 Filter,攔截的是請求地址 ,但提供更精細的的控制能力,這裡注意下必須過DispatcherServlet 的請求才會被攔截。
它允許你在請求處理前、處理後以及檢視渲染完成前執行自定義邏輯,可以用來對請求地址做一些認證授權、預處理,也可以計算一個請求的響應時間等,還可以處理跨域(CORS)問題。
簡單的執行流程描述:
- 請求到達 DispatcherServlet,然後傳送至 Interceptor,執行 preHandler;
- 請求到達 Controller,請求結束後,執行 postHandler。
2.2.2如何實現
- 建立 Interceptor 類,實現
HandlerInterceptor
介面,重寫 3 個方法,加@Component
註解。
@Component
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//請求開始時間
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
System.out.println("startTime : " + new Date(startTime));
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long startTime = (Long)request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
// 統計耗時
long executeTime = endTime - startTime;
System.out.println("executeTime : " + executeTime + "ms");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2.配置攔截器,實現WebMvcConfigurer
介面,加@Configuration
註解並重寫addInterceptors
方法。
@Configuration
public class MyWebConfigurer implements WebMvcConfigurer {
@Resource
private MyHandlerInterceptor myHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> patterns = new ArrayList<>();
patterns.add("/test/handlerInterceptor");
registry.addInterceptor(myHandlerInterceptor)
.addPathPatterns(patterns) // 需要攔截的請求
.excludePathPatterns(); // 不需要攔截的請求
}
}
攔截結果如下:
Spring 專案如何實現?
可透過使用mvc:interceptors
標籤來宣告需要加入到 SpringMVC 攔截器鏈中的攔截器。
2.3. MethodInterceptor 攔截器
2.3.1. 簡介
MethodInterceptor 是 AOP 中的攔截器,它攔截的目標是方法,可以不是 Controller 中的方法。
在對一些普通的方法上的攔截可以使用該攔截器,這是 HandlerInterceptor 無法實現的。
可用來進行方法級別的身份認證、授權以及日誌記錄等,也可基於自定義註解實現一些通用的方法增強功能。
2.3.2. 如何實現
MethodInterceptor 是基於 AOP 實現的,所以根據不同的代理有多種實現方式,更多的實現方式和原理我將在整理 Spring AOP 的時候詳細接受。
這裡我將介紹透過BeanNameAutoProxyCreator
自動代理實現攔截。該類是基於 Bean 名稱的自動代理,可以針對特定的Bean進行個性化的 AOP 配置。
1.建立簡單的需要攔截的方法。
public interface UserService {
public String getUser();
}
@Component
public class UserServiceImpl implements UserService{
@Override
public String getUser() {
return "我是福星";
}
}
2.建立 Interceptor 類,實現MethodInterceptor
介面,重寫invoke
方法,加@Component
註解。
@Component
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("進入攔截,方法執行前,攔截方法是:" + invocation.getMethod().getName());
Object result = invocation.proceed();
System.out.println("方法執行後");
return result;
}
}
3.配置自動代理,加@Configuration
註解並建立自動代理BeanNameAutoProxyCreator
。
@Configuration
public class MyMethodConfigurer {
@Resource
private MyMethodInterceptor myMethodInterceptor;
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
// 使用BeanNameAutoProxyCreator來建立代理
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
// 指定一組需要自動代理的Bean名稱,Bean名稱可以使用*萬用字元
beanNameAutoProxyCreator.setBeanNames("user*");
//設定攔截器名稱,這些攔截器是有先後順序的
beanNameAutoProxyCreator.setInterceptorNames("myMethodInterceptor");
return beanNameAutoProxyCreator;
}
}
發起請求後,呼叫該方法前會進行攔截。
3. 總結
過濾器一般用於對 Servlet 請求和響應進行通用性的處理,通常關注請求和響應內容,而不涉及具體的業務邏輯。而攔截器用於對 SpringMVC 的請求和響應進行特定的業務處理,通常與控制器層的請求處理有關。
不論是過濾器和攔截器,都可以有多個。執行順序上攔截器是由配置中的順序決定,而過濾器可透過@Component
+@Order
決定,也可由web.xml
檔案中的配置順序決定。
總的來說,攔截器的使用更加靈活,Filter 能做的事情,攔截器也能做。Filter 一般用於對 URL 請求做編碼處理、過濾無用引數、安全校驗(比如登陸態校驗),如果涉及業務邏輯上的,還是建議用攔截器。