咱們對全部的api都須要作一個全局的處理:好比:記錄全局處理時間、全局處理日誌,就須要用到攔截機制:常見攔截機制分爲以下幾種:java
應用場景和實現機制。 咱們以記錄全局全部時間爲例:web
新建過濾器:TimeFilterspring
@Component//注入到spring public class TimeFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("time filter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("time filter doFilter-->start"); long start = new Date().getTime(); filterChain.doFilter(servletRequest,servletResponse); long end = new Date().getTime(); System.out.println("time filter 耗時:"+(end-start)); System.out.println("time filter doFilter-->end"); } @Override public void destroy() { System.out.println("time filter destroy"); } }
app端調用DetailInfo接口:
後端
服務後臺結果:api
time filter doFilter-->start ====DetailInfo===== time filter 耗時:274 time filter doFilter-->end
有時候咱們使用過濾器時候是使用第三方的過濾器;咱們無法修改裏面的代碼.這個時候咱們如何去將第三方的過濾器注入到Spring裏面(添加不了@Component//注入到spring);app
傳統開發模式下,會有一個web.xml文件,咱們將第三方的過濾器添加到此web.xml文件下便可。在SpringBoot下添加沒有註解:@Component的類到spring下:框架
咱們定義一個註解修飾的類:此註解修飾的類,可當作 web.xml文件相似(此時咱們須要註釋掉TimeFilter的註解:@Component):async
@Configuration public class WebConfig { @Bean public FilterRegistrationBean timeFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); //1.注入bean TimeFilter timeFilter = new TimeFilter(); registrationBean.setFilter(timeFilter); //2.定義url List<String> urls = Arrays.asList("/*"); registrationBean.setUrlPatterns(urls); return registrationBean; } }
對別使用了@Component註解的過濾器和@Bean注入的過濾器:
1.@Bean能夠自定義過濾器過濾前綴 @Component修飾的不能夠,至關於所有url攔截。ide
過濾器說明: 1.Filter接受到的request請求 究竟是哪一個控制器的哪一個方法去處理,在Filter裏面是不知道的,由於filter是javax.servlet的J2EE規範。J2EE規範裏面其實不瞭解跟Spring相關的任何東西,而UserController是Spring MVC本身定義的東西。post
2.若是須要知道:是哪一個控制器的哪一個方法去處理信息的話就要使用到了攔截器;攔截器是Spring框架本身提供的
@Component //注意光聲明爲Component不能讓其起做用 還須要在:WebConfig 中繼承(extends) WebMvcConfigurerAdapter public class TimeInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /** * 1.在控制器方法調用前執行 * 2.Interceptor比Filter的一個優點是具備:handler * 3.return 返回值決定了後面方法是否要執行 */ HandlerMethod handlerMethod = (HandlerMethod) handler; System.out.println(handlerMethod.getBean().getClass().getName());//打印出類名 System.out.println(handlerMethod.getMethod().getName());//打印出方法名 request.setAttribute("startTime",new Date().getTime()); System.out.println("TimeInterceptor-->preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { /** * 在控制器方法調用成功後執行:若是方法拋出了異常將不會執行此方法 */ System.out.println("TimeInterceptor-->postHandle"); Long start = (Long)request.getAttribute("startTime"); System.out.println("TimeInterceptor 耗時:"+(new Date().getTime()-start)); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception { /** * 控制器方法無論成功仍是失敗都會進入到此方法 */ System.out.println("TimeInterceptor-->afterCompletion"); Long start = (Long)request.getAttribute("startTime"); System.out.println("TimeInterceptor 耗時:"+(new Date().getTime()-start)); System.out.println("TimeInterceptor ex is :"+e); } }
攔截器起做用還須要添加到InterceptorRegistry中
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Autowired private TimeInterceptor timeInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timeInterceptor); } @Bean public FilterRegistrationBean timeFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); //1.注入bean TimeFilter timeFilter = new TimeFilter(); registrationBean.setFilter(timeFilter); //2.定義url List<String> urls = Arrays.asList("/*"); registrationBean.setUrlPatterns(urls); return registrationBean; } }
使用App端測試:
後端打印日誌:
咱們修改用戶詳情接口:拋出一個異常
@GetMapping("/{id:\\d+}") @JsonView(User.UserDetailView.class) public User DetailInfo(@PathVariable(name = "id") String xxx){ throw new UserNotExistException(); }
App端請求:
後端打印出:
發現afterCompletion裏面的ex爲null緣由是咱們全局的異常處理類已經處理了這個異常;說明@ControllerAdvice修飾的GeneralExceptionHandler會在咱們攔截器afterCompletion執行以前處理。
攔截器特色:
1.攔截器相比於過濾器Filter,它可以拿到request裏面的類和方法,知道是哪一個類的哪一個方法去執行操做,可是有個問題是:它無法拿到方法上的參數值。 緣由:咱們查看Spring源碼:DispatcherServlet(分發咱們請求的)--> doService方法--> this.doDispatch(request, response);
//此時調用咱們攔截器的PreHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //真正進行方法參數封裝的方法是:handle mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; }
2.若是出了記錄類 和方法時候還須要知道具體參數內容,就須要使用切片:
須要知足:切入點 加強
1.在哪些方法上起做用是使用一個表達式表述的:
execution(* com.yxm.security.web.controller.UserController.*(..)) //第一個星-標識任何返回值、第二個星標識任何方法,(..)標識任何參數。 上面的意思是:在UserController類下的任何方法 任何參數 任何返回值都會執行切面
2.在何時起做用是使用4個註解來表述的:@After @Before @Around @AfterThrowing
@Around("execution(* com.yxm.security.web.controller.UserController.*(..))")
@Aspect @Component public class TimeAspect { @Around("execution(* com.yxm.security.web.controller.UserController.*(..))") public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{ /** * 切片:pointcut裏面包含了全部要執行方法的信息:類名 方法名 方法參數等 */ System.out.println("time aspect start"); Object[] args = pjp.getArgs(); for (Object arg : args) { System.out.println("arg is "+arg); } long start = new Date().getTime(); Object object = pjp.proceed();//其實就是調用被攔截的方法;相似於Filter中chain.doFilter(request,response) System.out.println("time aspect 耗時:"+ (new Date().getTime() - start)); System.out.println("time aspect end"); return object; } }
App端測試:
後端代碼:
執行順序: