Filter(過濾器)、interceptor(攔截器)和Aspect(切面)的使用及區別

在工作中如何選擇攔截機制去處理我們的業務請求,過濾器,攔截器,還是切面的選擇一直比較模糊,今天花時間整理一下

概述

1,Filter

首先,過濾器是服務端的一個組件,是基于servlet實現從客戶端訪問服務端web資源的一種攔截機制,對請求request和響應response都進行過濾,依賴于serverlet容器,使用時,實現Filter接口,在web.xml里配置對應的class還有mapping-url,springboot工程可以通FilterRegisteration配置后,設置要過濾的URL, 注意 兩種方式過濾器都是有序的,誰在前就先調用誰!定義過濾器后會重寫三個方法,分別是init(),doFilter(),和destory(),

  • init方法是過濾器的初始化方法,當web容器創建這個bean的時候就會執行,這個方法可以讀取web.xml里面的參數
  • doFilter方法是執行過濾的請求的核心,當客戶端請求訪問web資源時,這個時候我們可以拿到request里面的參數,對數據進行處理后,通過filterChain方法將請求將請求放行,放行后我們也可以通過response對響應進行處理(比如壓縮響應),然后會傳遞到下一個過濾器
  • destory方法是當web容器中的過濾器實例被銷毀時,會被執行,釋放資源
/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-12 19:36
 */
//@Component
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("過濾器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("過濾器執行了");
        long start2 = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        long time = System.currentTimeMillis() - start2;
        System.out.println("過濾器執行的時間是 :" + time);
        System.out.println("過濾器執行結束");
    }

    @Override
    public void destroy() {
        System.out.println("過濾器銷毀了");
    }
}</pre>

spring boot工程可以通過加@Component注解添加進spring管理,也可以通過下面注冊的方式去執行,推薦用下方的方式

@Bean
public FilterRegistrationBean timeFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    TimeFilter filter = new TimeFilter();
    filterRegistrationBean.setFilter(filter);
    filterRegistrationBean.addUrlPatterns("/user","/users");
    return filterRegistrationBean;
}
    接下來,我們可以看下圖示的過濾器執行流程和生命周期,就能很好的理解Filter了

流程圖


image.png

生命周期


image.png
2,interceptor(攔截器)

攔截器,顧名思義,他的作用就是攔截,這個要和過濾器區分開,過濾器依賴serverlet容器,獲取request和response處理,是基于函數回調,簡單說就是“去取你想取的”,攔截器是通過java反射機制,動態代理來攔截web請求,是“拒你想拒絕的”,他只攔截web請求,但不攔截靜態資源,Struts2里面就是將攔截器串聯,實現對請求的處理,下面以spring 的攔截器為例,寫個demo

/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-13 14:50
 */
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder) {
        out.println("攔截器.preHandle 開始執行。。。");
        out.println(hanlder.getClass().getSimpleName());
        out.println(((HandlerMethod) hanlder).getBean().getClass().getName());
        httpServletRequest.setAttribute("start", currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder, ModelAndView modelAndView) {
        out.println("攔截器.postHandle 開始執行。。。");
        long start = (long) httpServletRequest.getAttribute("start");
        out.println("postHandle執行時間為:" + (currentTimeMillis() - start));

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder, Exception e) {
        //會打印兩次 spring里面的basic error 也會被攔截
        out.println("攔截器.afterCompletion 開始執行。。。");
        long start = (long) httpServletRequest.getAttribute("start");
        out.println("afterCompletion執行時間為:" + (currentTimeMillis() - start));
        out.println("\n ex is :" + e+"\n");
    }
}

再來看看攔截器再spring boot里面的配置,首先要繼承WebMvcConfigurerAdapter適配器,重寫addIntercepors方法再調用register方法添加攔截器,前提,自定義的interceptor要加上spring注解被spring容器管理

/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-13 14:19
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    private final MyInterceptor myInterceptor;

    @Autowired
    public WebConfig(MyInterceptor myInterceptor) {
        this.myInterceptor = myInterceptor;
    }

//    @Bean
//    public FilterRegistrationBean timeFilter() {
//        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//        TimeFilter filter = new TimeFilter();
//        filterRegistrationBean.setFilter(filter);
//        filterRegistrationBean.addUrlPatterns("/user","/users");
//        return filterRegistrationBean;
//    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor);
    }
}

    我們再來看看控制臺的輸出
image.png

接下來來講講攔截器的三個方法,preHandle(),postHandle(),afterCompletion()

??:攔截器與過濾器方法內參數不同,多了一個Object handler,在請求進入控制層前,spring mvc 會將請求交給handler處理,handler參數就是用來描述處理請求的,從上面的demo可以看出來 他的類型是handlerMethod的,處理的請求的是BrowserAuthenticationController

  • preHandler(): 這個方法是在controller調用之前調用,通過返回true或者false決定是否進入Controller層
  • postHandler():在請求進入控制層之后調用,但是在處理請求拋出異常時不會調用
  • afterCompletion(): 在請求處理完成之后,也就是在DispatherServlet渲染了視圖之后執行,也就是說這個方法必定是執行,包含異常信息,它的主要作用就是清理資源

接下來總結一下過濾器和攔截的前后順序,看下圖:


image.png
3,Aspect(切片)

在使用過濾器的時能獲取request和response對象,對請求和響應進行處理,使用攔截器時,我們可以通過handler來獲取當前請求控制器的方法名稱,但是有一個弊端,我們拿不到控制器要接收的參數,先看下servlet源碼的執行順序

image.png

image.png

image.png

從DispatherServlet分發請求時,進入doService()方法內部,在方法參數封裝之前,添加了判斷,applyPreHandle()方法就時判斷攔截器里面的preHandler()方法,根據返回的true或者false,判斷是否執行真正的handler,所以我們在攔截器的handler參數里面是獲取不到請求的參數的,因此,我們要引入Spring AOP,也就是切片編程,它可以在控制器的執行之前,執行之后,拋出異常等等,進行控制!

切片編程,在網上看到了一個很貼切的說法,面對的是處理過程中的方法或者階段,以獲得各部分的低耦合性的隔離效果,它是基于動態代理,它關注的是行為和過程,它常用的注解為,下面通過spring boot 實現一個demo

  • @Aspect(聲明一個切面)
  • @Before(相當于攔截器preHandler,在方法執行前調用)
  • @After(相當于攔截器的afterComplement()在方法執行后調用)
  • @AfterThrowing(方法拋出異常時調用)
  • @AfterReturning(當方法返回時調用)
  • @Around(包含以上方的執行順序)
/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-13 16:31
 */
@Aspect
@Component
public class TimeAspect {

    @Around("execution(* com.wtzhou.security.controller.UserController.*(..))")
//    @After("")
//    @Before("")
//    @AfterThrowing()
//    @AfterReturning()
    public Object handlerControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object[] args = proceedingJoinPoint.getArgs();
        for (Object arg : args) {
            out.println("請求參數為:"+arg);
        }
        out.println("TimeAspect 切片開始執行");
        long start = currentTimeMillis();
        Object proceed = proceedingJoinPoint.proceed();
        out.println("切片執行耗時:" + (currentTimeMillis() - start));
        out.println("切片執行結束!");
        return proceed;
    }
}

通過注解內部的表達式不同,可以定義你想要的切入點,ProceedingJoinPoint對象可以當前的請求參數,對參數處理后,可以調用proceed方法放行,我們可以看看控制臺的輸出

image.png

image.png
總結一下
在代碼里面將過濾器,攔截器,切片,還有我們常用的@ControllerAdvice異常攔截機制注解放開時,我們來看看控制臺的輸出
image.png
    通過控制臺的日志 我們可以用一張簡單的圖來直觀展現出來
image.png

通過圖示:當收到請求響應時,執行的順序為filter--》interceptor--》ControllerAdvice--》Aspect,然后到大控制層,如果控制層拋出異常,最先也會被Aspect捕獲,如果未處理,會繼續向上一層拋出,如果到Filter也沒有處理的話,就會拋到容器內部

===== 結束 =====

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。