再擼一遍過濾器和攔截器

前言

不管是springmvc還是springboot,過濾器和攔截器在開發中總時經常用到的,我們對於過濾器、攔截器不應該是侷限在會使用上,需要更多的瞭解他們,這裏來和大家一起再擼一遍過濾器、攔截器。

過濾器 (Filter)

過濾器的配置比較簡單,直接實現Filter 接口即可,也可以通過@WebFilter註解實現對特定URL攔截,看到Filter 接口中定義了三個方法。

  • init() :該方法在容器啓動初始化過濾器時被調用,它在 Filter 的整個生命週期只會被調用一次。「注意」:這個方法必須執行成功,否則過濾器會不起作用。
  • doFilter() :容器中的每一次請求都會調用該方法, FilterChain 用來調用下一個過濾器 Filter
  • destroy():當容器銷燬 過濾器實例時調用該方法,一般在方法中銷燬或關閉資源,在過濾器 Filter 的整個生命週期也只會被調用一次
@Component
public class MyFilter 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("Filter 處理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("Filter 後置");
    }
}

攔截器 (Interceptor) 

攔截器它是鏈式調用,一個應用中可以同時存在多個攔截器Interceptor, 一個請求也可以觸發多個攔截器 ,而每個攔截器的調用會依據它的聲明順序依次執行。首先編寫一個簡單的攔截器處理類,請求的攔截是通過HandlerInterceptor 來實現,看到HandlerInterceptor 接口中也定義了三個方法。

  • preHandle() :這個方法將在請求處理之前進行調用。「注意」:如果該方法的返回值爲false ,將視爲當前請求結束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執行。
  • postHandle():只有在 preHandle() 方法返回值爲true 時纔會執行。會在Controller 中的方法調用之後,DispatcherServlet 返回渲染視圖之前被調用。「有意思的是」postHandle() 方法被調用的順序跟 preHandle() 是相反的,先聲明的攔截器  preHandle() 方法先執行,而postHandle()方法反而會後執行。
  • afterCompletion():只有在 preHandle() 方法返回值爲true 時纔會執行。在整個請求結束之後,  DispatcherServlet 渲染了對應的視圖之後執行。
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
     Object handler) throws Exception {
        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
     Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Interceptor 處理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
     Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor 後置");
    }
}

將自定義好的攔截器處理類進行註冊,並通過addPathPatternsexcludePathPatterns等屬性設置需要攔截或需要排除的 URL

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
    }
}

在比較中學習

  1. 實現原理不同

過濾器 和 攔截器 均體現了AOP的編程思想,都可以實現諸如日誌記錄、登錄鑑權等功能,但二者的不同點也是比較多的。首先底層實現方式大不相同,過濾器是基於函數回調的,攔截器則是基於Java的反射機制(動態代理)實現的。這裏重點說下過濾器!

在我們自定義的過濾器中都會實現一個 doFilter()方法,這個方法有一個FilterChain 參數,而實際上它是一個回調接口。ApplicationFilterChain是它的實現類, 這個實現類內部也有一個 doFilter() 方法就是回調方法。

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException,ServletException;
}

2、使用範圍不同

我們看到過濾器 實現的是 javax.servlet.Filter 接口,而這個接口是在Servlet規範中定義的,也就是說過濾器Filter 的使用要依賴於Tomcat等容器,導致它只能在web程序中使用。而攔截器(Interceptor)  它是一個Spring組件,並由Spring容器管理,並不依賴Tomcat等容器,是可以單獨使用的。不僅能應用在web程序中,也可以用於ApplicationSwing等程序中。

3、觸發時機不同

過濾器攔截器的觸發時機也不同,我們看下邊這張圖。

過濾器Filter是在請求進入容器後,但在進入servlet之前進行預處理,請求結束是在servlet處理完以後。攔截器 Interceptor 是在請求進入servlet後,在進入Controller之前進行預處理的,Controller 中渲染了對應的視圖之後請求結束。

4、注入Bean情況不同

在實際的業務場景中,應用到過濾器或攔截器,爲處理業務邏輯難免會引入一些service服務。下邊我們分別在過濾器和攔截器中都注入service,看看有什麼不同?

@Component
public class TestServiceImpl implements TestService {

    @Override
    public void a() {
        System.out.println("我是方法A");
    }
}

過濾器中注入service,發起請求測試一下 ,日誌正常打印出“我是方法A”

    @Autowired
    private TestService testService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
    FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter 處理中");
        testService.a();
        filterChain.doFilter(servletRequest, servletResponse);
    }

在攔截器中注入service,發起請求測試一下 ,竟然TM的報錯了,debug跟一下發現注入的service怎麼是Null啊?

這是因爲加載順序導致的問題,攔截器加載的時間點在springcontext之前,而Bean又是由spring進行管理。解決方案也很簡單,我們在註冊攔截器之前,先將Interceptor 手動進行注入。「注意」:在registry.addInterceptor()註冊的是getMyInterceptor() 實例。

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public MyInterceptor getMyInterceptor(){
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}

6、控制執行順序不同

實際開發過程中,會出現多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優先執行,就涉及到它們的執行順序。過濾器用@Order註解控制執行順序,通過@Order控制過濾器的級別,值越小級別越高越先執行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

攔截器默認的執行順序,就是它的註冊順序,也可以通過Order手動設置控制,值越小越先執行。

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
    }

看到輸出結果發現,先聲明的攔截器  preHandle() 方法先執行,而postHandle()方法反而會後執行。postHandle() 方法被調用的順序跟 preHandle() 居然是相反的!如果實際開發中嚴格要求執行順序,那就需要特別注意這一點。

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor2 處理中
Interceptor1 處理中
Interceptor 後置
Interceptor2 處理後
Interceptor1 處理後

「那爲什麼會這樣呢?」  得到答案就只能看源碼了,我們要知道controller 中所有的請求都要經過核心組件DispatcherServlet路由,都會執行它的 doDispatch() 方法,而攔截器postHandle()preHandle()方法便是在其中調用的。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
throws Exception {
    
        try {
         ...........
            try {
                // 獲取可以執行當前Handler的適配器
                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;
                    }
                }
                // 注意: 執行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 注意:執行Handle【包括我們的業務邏輯,當拋出異常時會被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                // 注意:執行Interceptor中PostHandle 方法【拋出異常時無法執行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

看看兩個方法applyPreHandle()applyPostHandle()具體是如何被調用的,就明白爲什麼postHandle()preHandle()  執行順序是相反的了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) 
    throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }
        return true;
    }
void applyPostHandle(HttpServletRequest request, HttpServletResponse response,
     @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.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);
            }
        }
    }

發現兩個方法中在調用攔截器數組 HandlerInterceptor[] 時,循環的順序竟然是相反的。。。,導致postHandle()preHandle() 方法執行的順序相反。

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