在工作中如何選擇攔截機制去處理我們的業務請求,過濾器,攔截器,還是切面的選擇一直比較模糊,今天花時間整理一下
概述
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也沒有處理的話,就會拋到容器內部