拦截器原理

  • 代理
    通过cglib/jdk proxy创建对象的代理实例

  • 回调
    生成代理对象就是对原对象功能的增强,修改字节码文件,在特定位置插入回调方法(目标对象的方法)

Spring-Boot自定义拦截器 - 图1

HandlerInterceptor介绍

Spring提供的拦截器Interceptor与Servlet中的Filter不同的是, Interceptor采用AOP的方式在Servlet的service方法执行之前进行拦截, 可以进行更精细的控制

Interceptor中有如下方法:

  • preHandle: 在Controller处理之前调用, 返回false时整个请求结束
  • postHandle: 在Controller调用之后执行, 但它会在DispatcherServlet进行视图的渲染之前执行, 也就是说在这个方法中你可以对ModelAndView进行操作
  • afterCompletion: 在整个请求完成之后执行, 也就是DispatcherServlet已经渲染了视图之后执行; 这个方法的主要作用是用于清理资源的
  • afterConcurrentHandlingStarted: 这个方法是AsyncHandlerInterceptor接口中添加的. 当Controller中有异步请求方法的时候会触发该方法, 异步请求先支持preHandle、然后执行afterConcurrentHandlingStarted, 异步线程完成之后执行会再执行preHandle、postHandle、afterCompletion

spring boot自定义拦截器实现

拦截器

  1. public class AuthInterceptor extends HandlerInterceptorAdapter {
  2. /**
  3. * 前置检查
  4. */
  5. @Override
  6. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  7. if (!handler.getClass().isAssignableFrom(HandlerMethod.class)) {
  8. return true;
  9. }
  10. String ip = request.getRemoteAddr();
  11. long startTime = System.currentTimeMillis();
  12. request.setAttribute("requestStartTime", startTime);
  13. HandlerMethod handlerMethod = (HandlerMethod) handler;
  14. Method method = handlerMethod.getMethod();
  15. System.out.println("用户:" + ip + ",访问目标:" + method.getDeclaringClass().getName() + "." + method.getName());
  16. return true;
  17. }
  18. /**
  19. * Controller调用之后执行
  20. */
  21. @Override
  22. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
  23. HandlerMethod handlerMethod = (HandlerMethod) handler;
  24. Method method = handlerMethod.getMethod();
  25. long startTime = (Long) request.getAttribute("requestStartTime");
  26. long endTime = System.currentTimeMillis();
  27. long executeTime = endTime - startTime;
  28. // log it
  29. if (executeTime > 1000) {
  30. System.out.println("[" + method.getDeclaringClass().getName() + "." + method.getName() + "] 执行耗时 : "
  31. + executeTime + "ms");
  32. } else {
  33. System.out.println("[" + method.getDeclaringClass().getSimpleName() + "." + method.getName() + "] 执行耗时 : "
  34. + executeTime + "ms");
  35. }
  36. }
  37. }

将拦截器加入springmvc中

1.继承WebMvcConfigurerAdapter(spring5该方法已过时)
  1. @Configuration
  2. public class AuthHandlerAdapter extends WebMvcConfigurerAdapter {
  3. /**
  4. * 拦截器
  5. * 由于项目集成了swagger,这里直接不拦截swagger的相关请求
  6. */
  7. @Override
  8. public void addInterceptors(InterceptorRegistry registry) {
  9. //AuthInterceptor就是我们自定义的拦截器
  10. registry.addInterceptor(new AuthInterceptor())
  11. .addPathPatterns("/**")
  12. .excludePathPatterns("/swagger-ui.html")
  13. .excludePathPatterns("/swagger-resources/**")
  14. .excludePathPatterns("/v2/api-docs");
  15. }
  16. /**
  17. * 资源处理器
  18. * swagger会和freemarker的静态资源路径冲突因此需配置swagger的资源处理器
  19. */
  20. @Override
  21. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  22. registry.addResourceHandler("/swagger-ui.html")
  23. .addResourceLocations("classpath:/META-INF/resources/");
  24. registry.addResourceHandler("/webjars/**")
  25. .addResourceLocations("classpath:/META-INF/resources/webjars/");
  26. }
  27. }

2.继承WebMvcConfigurationSupport
  1. @Configuration
  2. public class AppConfiguration extends WebMvcConfigurationSupport {
  3. @Override
  4. public void addInterceptors(InterceptorRegistry registry) {
  5. registry.addInterceptor(new CommonInterceptor()).addPathPatterns("/**");
  6. registry.addInterceptor(new UserInterceptor()).addPathPatterns("/manage/**").addPathPatterns("/user/logout");
  7. super.addInterceptors(registry);
  8. }
  9. @Override
  10. protected void addResourceHandlers(ResourceHandlerRegistry registry) {
  11. registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
  12. super.addResourceHandlers(registry);
  13. }
  14. }

特别注意:

  • 重写addInterceptors的同时,addResourceHandlers也要重写

  • 继承WebMvcConfigurationSupport会使Springboot中默认的WebMvcAutoConfiguration不初始化,

  • WebMvc的配置需要自己手动实现,配置文件中的spring.mvc.xxxspring.resources.xxx也会不起作用

  • 不建议使用

    1. @Configuration(proxyBeanMethods = false)
    2. @ConditionalOnWebApplication(type = Type.SERVLET)
    3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    4. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //WebMvcConfigurationSupport不存在才会实例化该类
    5. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    6. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    7. ValidationAutoConfiguration.class })
    8. public class WebMvcAutoConfiguration {
    9. xxx
    10. }

3.实现WebMvcConfigurer
  1. @Bean
  2. public WebMvcConfigurer webMvcConfig(){
  3. return new WebMvcConfigurer() {
  4. @Override
  5. public void addInterceptors(InterceptorRegistry registry) {
  6. registry.addInterceptor(new CommonInterceptor()).addPathPatterns("/**");
  7. registry.addInterceptor(new UserInterceptor()).addPathPatterns("/manage/**").addPathPatterns("/user/logout");
  8. }
  9. };
  10. }

总结

  1. springboot默认可以访问以下路径文件(见ResourceProperties):
    classpath:/static
    classpath:/public
    classpath:/resources
    classpath:/META-INF/resources

  2. @EnableWebMvc、WebMvcConfigurationSupport、WebMvcConfigurationAdapter、WebMvcAutoConfiguration

    • @EnableWebMvc=WebMvcConfigurationSupport
      使用了@EnableWebMvc注解等于扩展了WebMvcConfigurationSupport但是没有重写任何方法

    • @EnableWebMvc+extends WebMvcConfigurationAdapter
      在扩展的类中重写父类的方法即可,这种方式会屏蔽springboot的WebMvcAutoConfiguration中的设置

    • @EnableWebMvc+extends WebMvcConfigurationSupport
      只会使用@EnableWebMvc。 extends WebMvcConfigurationSupport,在扩展的类中重写父类的方法即可,这种方式会屏蔽springboot的WebMvcAutoConfiguration中的设置

    • extends WebMvcConfigurationAdapter(spring5中已过时)
      在扩展的类中重写父类的方法即可,这种方式依旧使用springboot的WebMvcAutoConfiguration中的设置。 在springboot2.x中,WebMvcConfigurationAdapter已经过时,通过实现接口WebMvcConfigurer可以替代原有规则

    • 实现WebMvcConfigurer(推荐使用)
      自定义配置+WebMvcAutoConfiguration(默认配置)同时生效