過濾器Filter和攔截器Interceptor的聯(lián)系和區(qū)別
本文將對攔截器Interceptor進(jìn)行簡單講解,并通過幾個例子對它們的差異進(jìn)行簡要分析。
攔截器Interceptor簡介
一個應(yīng)用中可以定義多個攔截器,spring在項目啟動時,會將這些攔截器注冊進(jìn)來,并按照默認(rèn)規(guī)則進(jìn)行排序。如果是自定義的攔截器,可手動設(shè)置攔截器調(diào)用的先后順序。
各攔截器是鏈?zhǔn)秸{(diào)用,一個請求可以觸發(fā)多個攔截器,每個攔截器的調(diào)用會按照它加載到spring中的順序依次執(zhí)行。
攔截器中有3個方法,功能如下:
- preHandle:該方法在調(diào)用Controller方法或獲取靜態(tài)資源前被調(diào)用(靜態(tài)資源包括html、js等)。
- postHandle:該方法在調(diào)用Controller方法或獲取靜態(tài)資源后,但是視圖還沒有被渲染前調(diào)用。
- afterCompletion:該方法在視圖渲染后進(jìn)行調(diào)用,主要用來清除資源。
自定義攔截器Interceptor
- public class MyInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- System.out.println("調(diào)用preHandle");
- return true;
- }
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
- System.out.println("調(diào)用postHandle");
- }
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- System.out.println("調(diào)用afterCompletion");
- }
- }
注冊自定義攔截器Interceptor
編寫配置類并實現(xiàn)WebMvcConfigurer接口。
- @Configuration
- public class MyWebConfig implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
- }
- }
過濾器Filter和攔截器Interceptor區(qū)別
過濾器和攔截器都可以實現(xiàn)例如編碼設(shè)置、日志記錄、權(quán)限控制等功能,但是二者還是有很多區(qū)別的。
(1)實現(xiàn)原理不同
Filter是基于函數(shù)回調(diào)實現(xiàn)的:
每個自定義過濾器都會實現(xiàn)一個doFilter()方法,這個方法有一個關(guān)鍵參數(shù)FilterChain。它是一個回調(diào)接口,ApplicationFilterChain是它的具體實現(xiàn)類,該類內(nèi)部也有一個doFilter()方法,這個方法就是回調(diào)方法(ps:可以理解為方法遞歸調(diào)用,查看源碼比較容易理解)。
假設(shè)有2個濾器,調(diào)用流程圖如下(ps:畫的比較抽象):
Interceptor是基于反射實現(xiàn)的:
為什么說攔截器是基于反射實現(xiàn)的呢?個人理解在攔截器內(nèi)部的三個方法中,都有一個共同的參數(shù)handler,這個參數(shù)里包含的信息比較豐富。包含該請求所對應(yīng)的方法、方法所在的Controller、方法參數(shù)等信息,而這些信息都是spring通過反射加載進(jìn)來的。正是由于這些豐富的參數(shù),使得攔截的功能相比過濾器功能更強大。
handler參數(shù)信息
(2)使用范圍不同
過濾器Filter實現(xiàn)了javax.servlet.Filter接口,也就是說過濾器的使用要依賴于Tomcat等容器,所以它只能在web程序中使用。
攔截器Interceptor實現(xiàn)了
org.springframework.web.servlet接口,它是由Spring容器進(jìn)行管理,并不依賴Tomcat等容器,既可以應(yīng)用在web程序中,也可以應(yīng)用在非web程序中。
(3)觸發(fā)時機不同
過濾器Filter是在請求進(jìn)入Tomcat等容器后,servlet處理之前進(jìn)行調(diào)用的。
攔截器Interceptor是在請求進(jìn)入servlet后,執(zhí)行Controller之前進(jìn)行調(diào)用的。
(4)攔截范圍不同
過濾器Filter幾乎可以攔截所有進(jìn)入容器的請求。
攔截器Interceptor只會對Controller請求或訪問static目錄下的靜態(tài)資源請求起作用。
(5)初始化時機不同
過濾器Filter是隨著Tomcat等web容器啟動時而進(jìn)行初始化。
攔截器Interceptor時隨著spring啟動而進(jìn)行初始化。
過濾器和攔截器如何注入依賴服務(wù)
在實際開發(fā)中,當(dāng)使用到過濾器或攔截器時,難免會引入一些依賴的service服務(wù)。下面就通過例子進(jìn)行簡要說明:
Filter依賴service:直接采用注解@Autowired即可。
- @WebFilter(urlPatterns = {"/user/*"})
- @Log4j2
- public class MyFilter implements Filter {
- @Autowired
- private UserService userService;
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- log.info("Filter獲取到請求地址:" + httpServletRequest.getServletPath());
- filterChain.doFilter(request, response);
- HttpServletResponse httpServletResponse = (HttpServletResponse) response;
- log.info("Filter獲取到響應(yīng)類型:" + httpServletResponse.getContentType());
- }
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- System.out.println("Filter隨著項目的啟動而啟動,只初始化一次");
- }
- @Override
- public void destroy() {
- System.out.println("Filter隨著web項目的停止而銷毀,完成資源回收");
- }
- }
Interceptor依賴service:直接采用注解@Autowired,但是在將攔截器注入到spring容器中時,不能自己通過new來進(jìn)行創(chuàng)建。需要將攔截器當(dāng)做一個普通的bean注入到spring容器中,這樣就可以將service注入到攔截器中。
- public class MyInterceptor implements HandlerInterceptor {
- @Autowired
- private UserService userService;
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- System.out.println("調(diào)用preHandle");
- return true;
- }
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
- System.out.println("調(diào)用postHandle");
- }
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- System.out.println("調(diào)用afterCompletion");
- }
- }
- @Configuration
- public class MyWebConfig implements WebMvcConfigurer {
- @Bean
- public MyInterceptor getMyInterceptor(){
- return new MyInterceptor();
- }
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // getMyInterceptor()這種注冊方式可以在攔截器里注入bean
- registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
- // 這種注冊方式由于是自己new出來的,所以在攔截器里注冊的bean都為null
- registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");
- }
- }
過濾器和攔截器如何指定加載順序
(1)Filter:需要通過配置類指定加載順序,值越小,越先執(zhí)行。采用@WebFilter無法指定順序。
- @Bean
- public FilterRegistrationBean myFilter(){
- MyFilter myFilter = new MyFilter();
- FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
- filterRegistrationBean.setUrlPatterns(Arrays.asList("/user/*"));
- filterRegistrationBean.setOrder(2);
- return filterRegistrationBean;
- }
(2)Interceptor:需要通過配置類指定加載順序,值越小,越先執(zhí)行。
- @Configuration
- public class MyWebConfig implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // getMyInterceptor()這種注冊方式可以在攔截器里注入bean
- registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(1);
- // 這種注冊方式由于是自己new出來的,所以在攔截器里注冊的bean都為null
- registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
- }
- }
過濾器Filter和攔截器Interceptor使用場景
二者相比攔截器功能更強大些,F(xiàn)ilter能做的事情,它都能做,而且可以在請求前,請求后執(zhí)行,比較靈活。Filter主要用來設(shè)置字符編碼、過濾敏感詞匯和URL級別的簡單權(quán)限控制。如果需要記錄比較詳細(xì)的信息或比較復(fù)雜的權(quán)限管理,還是建議用攔截器實現(xiàn)。
拓展
Servlet和Controller的區(qū)別是什么?
使用Servlet可以收集來自網(wǎng)頁表單的用戶輸入,還可以動態(tài)創(chuàng)建網(wǎng)頁。DispatcherServlet是SpringMVC中唯一的Servlet,Servlet容器(Tomcat)把所有的請求都轉(zhuǎn)發(fā)到DispatcherServlet,然后通過HandlerMapping把請求路由到具體的Controller中。因此,Controller只是一個普通的Java Bean。