咱们对全部的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端测试:
后端代码:
执行顺序: