在開發(fā) Web 項目的時候炫贤,經(jīng)常需要過濾器來處理一些請求,包括字符集轉換什么的付秕,記錄請求日志什么的等等兰珍。在之前的 Web 開發(fā)中,我們習慣把過濾器配置到 web.xml 中询吴,但是在 SpringBoot 中掠河,兵沒有這個配置文件励幼,該如何操作呢?其實在 Spingboot 中存在3種形式進行過濾操作口柳。
1苹粟、使用傳統(tǒng)的過濾器
首先構建一個包,該包需要在項目啟動下面跃闹,如下圖
其中1代表是微服務啟動類嵌削,2代表在啟動類下面構建一個包,再在堡壘新建一個過濾器類望艺,并且實現(xiàn) Filter 接口
接下來實現(xiàn)里面的方法苛秕,這里我們僅僅是記錄方法調(diào)用鎖花費的時間。當然為了 SpringBoot 能夠識別這個組件找默,需要注解@Component
@Component
public class TimerFilter implements Filter{
@Override
public void destroy() {
System.out.println("timer Filter is destoried");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("timer Filter begin");
long start=new Date().getTime();
chain.doFilter(request, response);
long end=new Date().getTime();
System.out.println("timer Filter end,cost time:"+(end-start));
}
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("timer Filter is inited");
}
}
構建一個測試 Controller艇劫,方便測試
@RestController
public class UserController {
@GetMapping("/filter")
public String testFilter(){
return "filter is ok";
}
}
啟動項目,進行測試惩激。
項目啟動后店煞,首先看到過濾器里面的初始化方法被執(zhí)行了
接著訪問http://localhost:8888/filter
,控制臺打印出如下內(nèi)容风钻,表示過濾器正常調(diào)用
第三方過濾器的使用
有時候顷蟀,我們使用的是第三方的過濾器,并不是在我們項目啟動類注解可掃描的部分骡技,也沒法配置到 web.xml 里面鸣个,這個時候該怎么辦?
我們可以使用 SpringBoot 的配置類進行配置布朦。
首先構建一個包囤萤,再新建一個配置類,然后添加注解為@Configuration
接下來是趴,我們就開始注入 bean涛舍,這個 bean 是FilterRegistrationBean
,具體代碼如下
@Configuration
public class ProjectConfig {
@Bean
public FilterRegistrationBean timerFilter(){
FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean();
filterRegistrationBean.setFilter(new TimerFilter());
List<String>urls=Lists.newArrayList();
urls.add("/*");
filterRegistrationBean.setUrlPatterns(urls);
return filterRegistrationBean;
}
}
這里可以添加過濾器鎖攔截的 URL右遭,攔截更加精準做盅。
重新運行項目缤削,不出意外窘哈,將會得到同樣的結論。
2亭敢、使用Interceptor
由于上面的過濾器的過來方法里面是使用的ServletRequest request, ServletResponse response
滚婉,所以和 Spring 相關的上下文就很難獲得,也不知道是從哪個 Controller 來的帅刀,所以让腹,就出現(xiàn)了 SpringBoot 框架自帶的過濾器interceptor
.
首先構建包cn.ts.demo.interceptor
远剩,并且新建TimerInterceptor
類,該類需要實現(xiàn)HandlerInterceptor
可以看到有三個需要實現(xiàn)的方法骇窍,從方法名稱可以得知每個方法的具體作用瓜晤。
preHandle
:表示在調(diào)用某個方法之前,會調(diào)用
postHandle
:表示控制器的方法調(diào)用之后腹纳,該方法用調(diào)用痢掠;如果控制器的方法跑出異常,那么這個方法不會被執(zhí)行嘲恍。
afterCompletion
:表示無論控制器方法如何處理足画,該方法都會調(diào)用。
現(xiàn)在來完善方法里面的內(nèi)容佃牛,以及對象 Object 是什么淹辞。當然也是需要標注為 @Component
.
@Component
public class TimerInterceptor implements HandlerInterceptor{
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse arg1, Object arg2, Exception exception)
throws Exception {
System.out.println("afterCompletion exe");
Long start=(Long)request.getAttribute("startTimer");
System.out.println("afterCompletion花費時間:"+(new Date().getTime()-start));
System.out.println("異常:"+exception);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
System.out.println("postHandle exe");
Long start=(Long)request.getAttribute("startTimer");
System.out.println("postHandle花費時間:"+(new Date().getTime()-start));
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse arg1, Object obj) throws Exception {
System.out.println("preHandle exe");
//向 request 里面放入一個屬性
request.setAttribute("startTimer", new Date().getTime());
//查看這里的 obj 是什么
System.out.println("類名稱:"+((HandlerMethod)obj).getBean().getClass().getName());
System.out.println("方法名稱:"+((HandlerMethod)obj).getMethod().getName());
return true;
}
}
如此操作之后,并不能直接使用俘侠,需要在配置類里面進行配置象缀,同時修改配置繼承WebMvcConfigurerAdapter
,然后覆蓋addInterceptors
方法。這個方法需要把剛才做好的TimerInterceptor
增加進來爷速。當然需要把TimerInterceptor
注入進來攻冷,
@Autowired private TimerInterceptor timerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timerInterceptor);
}
現(xiàn)在可以重啟項目,開始驗證遍希。不出意外等曼,就得到如下結果
確實能夠得到相關的類和方法名稱。
如果我們的控制器方法跑出異常凿蒜,再來看下禁谦,修改下控制器的方法。
@GetMapping("/filter")
public String testFilter(){
throw new RuntimeException();
//return "filter is ok";
}
繼續(xù)重啟废封,再運行
得到的結論:
postHandle
不會執(zhí)行了州泊,直接跳到afterCompletion
。需要注意的是漂洋,如果有異常處理機制遥皂,也不會再afterCompletion
捕獲到異常。
3刽漂、切片 Aspect
雖然 Interceptor 能夠拿到類和方法名稱演训,但是不能夠拿到方法的參數(shù)和他的值。
查看下 Spring 的源碼贝咙,找到 DispatcherServlet
,這個是用來分發(fā)請求的样悟,找 doService
方法,再找到doDispatch(request, response);
,大概在901行,進入這個方法,找到962-967行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
其中mappedHandler.applyPreHandle
就是調(diào)用我們攔截器的pre部分窟她。如果為 false陈症,就不再執(zhí)行下面內(nèi)容。接下來才是真正執(zhí)行震糖,也就是ha.handle
這個方法录肯,里面包含了參數(shù)的拼裝,也就是說控制器的參數(shù)對象是有這個方法通過 request 里面的參數(shù)來構造出來的吊说。所以在 interceptor的 prehandle 方法里面是不知道參數(shù)是什么樣的嘁信。 所以如果需要知道具體的參數(shù),就得使用切片來處理疏叨。
Spring AOP 簡介
一個切片需要切入點和最強兩個部分潘靖。
大概了解了切片之后,我們需要立馬實現(xiàn)他蚤蔓。
首先還是先建立個 aspect 包卦溢,然后新建一個切片類TimerAspect
。需要增加注解秀又。
接下來单寂,需要創(chuàng)建切入點,時間點的說明
Before 雷同 interceptor 的 preHandle吐辙;
After 雷同 interceptor 的 postHandle宣决;
AfterThrow 雷同 interceptor 的 afterCompletion;
Around 是包圍全部昏苏,也就是覆蓋上面3個尊沸,一般用這個。
哪些方法上執(zhí)行是一些正則表達式贤惯,在上述注解里面洼专,比如
@Around("execution(* cn.ts.demo.controller.UserController.*(..))")
第一個*表示任何的返回值;
UserController后面的點星代表該類里面的所有方法孵构;
括號點點表示方法參數(shù)任意屁商。
關羽如何編寫這樣的表達式,可以參考[AOP參考]https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-pointcuts
接下來繼續(xù)完善該切片代碼
@Aspect
@Component
public class TimerAspect {
@Around("execution(* cn.ts.demo.controller.UserController.*(..))")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("time aspect start");
//方法參數(shù)
Object[] args = pjp.getArgs();
for (Object arg:args) {
System.out.println("參數(shù):"+arg);
}
Long start=new Date().getTime();
Object obj=pjp.proceed();
System.out.println("time aspect花費時間:"+(new Date().getTime()-start));
System.out.println("time aspect end");
return obj;
}
}
重啟服務颈墅,進行測試蜡镶,
由于我們的測試方法沒有參數(shù),所以參數(shù)打印不存在恤筛。
修改下控制器方法的代碼
@GetMapping("/filter/{id:\\d+}")
public String testFilter(@PathVariable String id){
//throw new RuntimeException();
return "filter is ok";
}
然后測試
不出意外官还,參數(shù)應該可以正常打印出來。
這樣我們把三種過濾器的方法做了說明叹俏,也能看得出默認的順序是過濾器妻枕,interceptor僻族,aspect粘驰,實際開發(fā)可能要綜合使用屡谐,以便達到我們需要的效果。