在工作中如何選擇攔截機(jī)制去處理我們的業(yè)務(wù)請求,過濾器别智,攔截器宗苍,還是切面的選擇一直比較模糊,今天花時(shí)間整理一下
概述
1薄榛,F(xiàn)ilter
首先讳窟,過濾器是服務(wù)端的一個(gè)組件,是基于servlet實(shí)現(xiàn)從客戶端訪問服務(wù)端web資源的一種攔截機(jī)制敞恋,對請求request和響應(yīng)response都進(jìn)行過濾丽啡,依賴于serverlet容器,使用時(shí)硬猫,實(shí)現(xiàn)Filter接口补箍,在web.xml里配置對應(yīng)的class還有mapping-url,springboot工程可以通FilterRegisteration配置后,設(shè)置要過濾的URL啸蜜, 注意 兩種方式過濾器都是有序的坑雅,誰在前就先調(diào)用誰!定義過濾器后會(huì)重寫三個(gè)方法衬横,分別是init(),doFilter(),和destory(),
- init方法是過濾器的初始化方法裹粤,當(dāng)web容器創(chuàng)建這個(gè)bean的時(shí)候就會(huì)執(zhí)行,這個(gè)方法可以讀取web.xml里面的參數(shù)
- doFilter方法是執(zhí)行過濾的請求的核心蜂林,當(dāng)客戶端請求訪問web資源時(shí)遥诉,這個(gè)時(shí)候我們可以拿到request里面的參數(shù)拇泣,對數(shù)據(jù)進(jìn)行處理后,通過filterChain方法將請求將請求放行矮锈,放行后我們也可以通過response對響應(yīng)進(jìn)行處理(比如壓縮響應(yīng))挫酿,然后會(huì)傳遞到下一個(gè)過濾器
- destory方法是當(dāng)web容器中的過濾器實(shí)例被銷毀時(shí),會(huì)被執(zhí)行愕难,釋放資源
/**
* @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("過濾器執(zhí)行了");
long start2 = System.currentTimeMillis();
filterChain.doFilter(servletRequest, servletResponse);
long time = System.currentTimeMillis() - start2;
System.out.println("過濾器執(zhí)行的時(shí)間是 :" + time);
System.out.println("過濾器執(zhí)行結(jié)束");
}
@Override
public void destroy() {
System.out.println("過濾器銷毀了");
}
}</pre>
spring boot工程可以通過加@Component注解添加進(jìn)spring管理,也可以通過下面注冊的方式去執(zhí)行惫霸,推薦用下方的方式
@Bean
public FilterRegistrationBean timeFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
TimeFilter filter = new TimeFilter();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.addUrlPatterns("/user","/users");
return filterRegistrationBean;
}
接下來猫缭,我們可以看下圖示的過濾器執(zhí)行流程和生命周期,就能很好的理解Filter了
流程圖
生命周期
2壹店,interceptor(攔截器)
攔截器猜丹,顧名思義,他的作用就是攔截硅卢,這個(gè)要和過濾器區(qū)分開射窒,過濾器依賴serverlet容器,獲取request和response處理将塑,是基于函數(shù)回調(diào)脉顿,簡單說就是“去取你想取的”,攔截器是通過java反射機(jī)制点寥,動(dòng)態(tài)代理來攔截web請求艾疟,是“拒你想拒絕的”,他只攔截web請求敢辩,但不攔截靜態(tài)資源蔽莱,Struts2里面就是將攔截器串聯(lián),實(shí)現(xiàn)對請求的處理戚长,下面以spring 的攔截器為例盗冷,寫個(gè)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 開始執(zhí)行。同廉。仪糖。");
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 開始執(zhí)行。迫肖。乓诽。");
long start = (long) httpServletRequest.getAttribute("start");
out.println("postHandle執(zhí)行時(shí)間為:" + (currentTimeMillis() - start));
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder, Exception e) {
//會(huì)打印兩次 spring里面的basic error 也會(huì)被攔截
out.println("攔截器.afterCompletion 開始執(zhí)行。咒程。鸠天。");
long start = (long) httpServletRequest.getAttribute("start");
out.println("afterCompletion執(zhí)行時(shí)間為:" + (currentTimeMillis() - start));
out.println("\n ex is :" + e+"\n");
}
}
再來看看攔截器再spring boot里面的配置,首先要繼承WebMvcConfigurerAdapter適配器帐姻,重寫addIntercepors方法再調(diào)用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);
}
}
我們再來看看控制臺(tái)的輸出
接下來來講講攔截器的三個(gè)方法,preHandle(),postHandle(),afterCompletion()
??:攔截器與過濾器方法內(nèi)參數(shù)不同,多了一個(gè)Object handler,在請求進(jìn)入控制層前剥纷,spring mvc 會(huì)將請求交給handler處理痹籍,handler參數(shù)就是用來描述處理請求的,從上面的demo可以看出來 他的類型是handlerMethod的晦鞋,處理的請求的是BrowserAuthenticationController
- preHandler(): 這個(gè)方法是在controller調(diào)用之前調(diào)用蹲缠,通過返回true或者false決定是否進(jìn)入Controller層
- postHandler():在請求進(jìn)入控制層之后調(diào)用,但是在處理請求拋出異常時(shí)不會(huì)調(diào)用
- afterCompletion(): 在請求處理完成之后悠垛,也就是在DispatherServlet渲染了視圖之后執(zhí)行线定,也就是說這個(gè)方法必定是執(zhí)行,包含異常信息确买,它的主要作用就是清理資源
接下來總結(jié)一下過濾器和攔截的前后順序斤讥,看下圖:
3,Aspect(切片)
在使用過濾器的時(shí)能獲取request和response對象湾趾,對請求和響應(yīng)進(jìn)行處理芭商,使用攔截器時(shí),我們可以通過handler來獲取當(dāng)前請求控制器的方法名稱搀缠,但是有一個(gè)弊端铛楣,我們拿不到控制器要接收的參數(shù),先看下servlet源碼的執(zhí)行順序
從DispatherServlet分發(fā)請求時(shí)艺普,進(jìn)入doService()方法內(nèi)部蛉艾,在方法參數(shù)封裝之前,添加了判斷衷敌,applyPreHandle()方法就時(shí)判斷攔截器里面的preHandler()方法勿侯,根據(jù)返回的true或者false,判斷是否執(zhí)行真正的handler缴罗,所以我們在攔截器的handler參數(shù)里面是獲取不到請求的參數(shù)的助琐,因此,我們要引入Spring AOP面氓,也就是切片編程兵钮,它可以在控制器的執(zhí)行之前,執(zhí)行之后舌界,拋出異常等等掘譬,進(jìn)行控制!
切片編程呻拌,在網(wǎng)上看到了一個(gè)很貼切的說法葱轩,面對的是處理過程中的方法或者階段,以獲得各部分的低耦合性的隔離效果,它是基于動(dòng)態(tài)代理靴拱,它關(guān)注的是行為和過程垃喊,它常用的注解為,下面通過spring boot 實(shí)現(xiàn)一個(gè)demo
- @Aspect(聲明一個(gè)切面)
- @Before(相當(dāng)于攔截器preHandler袜炕,在方法執(zhí)行前調(diào)用)
- @After(相當(dāng)于攔截器的afterComplement()在方法執(zhí)行后調(diào)用)
- @AfterThrowing(方法拋出異常時(shí)調(diào)用)
- @AfterReturning(當(dāng)方法返回時(shí)調(diào)用)
- @Around(包含以上方的執(zhí)行順序)
/**
* @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("請求參數(shù)為:"+arg);
}
out.println("TimeAspect 切片開始執(zhí)行");
long start = currentTimeMillis();
Object proceed = proceedingJoinPoint.proceed();
out.println("切片執(zhí)行耗時(shí):" + (currentTimeMillis() - start));
out.println("切片執(zhí)行結(jié)束本谜!");
return proceed;
}
}
通過注解內(nèi)部的表達(dá)式不同,可以定義你想要的切入點(diǎn)偎窘,ProceedingJoinPoint對象可以當(dāng)前的請求參數(shù)乌助,對參數(shù)處理后,可以調(diào)用proceed方法放行陌知,我們可以看看控制臺(tái)的輸出
總結(jié)一下
在代碼里面將過濾器他托,攔截器,切片纵诞,還有我們常用的@ControllerAdvice異常攔截機(jī)制注解放開時(shí),我們來看看控制臺(tái)的輸出
通過控制臺(tái)的日志 我們可以用一張簡單的圖來直觀展現(xiàn)出來
通過圖示:當(dāng)收到請求響應(yīng)時(shí)培遵,執(zhí)行的順序?yàn)閒ilter--》interceptor--》ControllerAdvice--》Aspect浙芙,然后到大控制層,如果控制層拋出異常籽腕,最先也會(huì)被Aspect捕獲嗡呼,如果未處理,會(huì)繼續(xù)向上一層拋出皇耗,如果到Filter也沒有處理的話南窗,就會(huì)拋到容器內(nèi)部