拦截器原理
代理
通过cglib/jdk proxy创建对象的代理实例回调
生成代理对象就是对原对象功能的增强,修改字节码文件,在特定位置插入回调方法(目标对象的方法)
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自定义拦截器实现
拦截器
public class AuthInterceptor extends HandlerInterceptorAdapter {
/**
* 前置检查
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!handler.getClass().isAssignableFrom(HandlerMethod.class)) {
return true;
}
String ip = request.getRemoteAddr();
long startTime = System.currentTimeMillis();
request.setAttribute("requestStartTime", startTime);
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
System.out.println("用户:" + ip + ",访问目标:" + method.getDeclaringClass().getName() + "." + method.getName());
return true;
}
/**
* Controller调用之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
long startTime = (Long) request.getAttribute("requestStartTime");
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
// log it
if (executeTime > 1000) {
System.out.println("[" + method.getDeclaringClass().getName() + "." + method.getName() + "] 执行耗时 : "
+ executeTime + "ms");
} else {
System.out.println("[" + method.getDeclaringClass().getSimpleName() + "." + method.getName() + "] 执行耗时 : "
+ executeTime + "ms");
}
}
}
将拦截器加入springmvc中
1.继承WebMvcConfigurerAdapter
(spring5该方法已过时)
@Configuration
public class AuthHandlerAdapter extends WebMvcConfigurerAdapter {
/**
* 拦截器
* 由于项目集成了swagger,这里直接不拦截swagger的相关请求
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//AuthInterceptor就是我们自定义的拦截器
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/swagger-ui.html")
.excludePathPatterns("/swagger-resources/**")
.excludePathPatterns("/v2/api-docs");
}
/**
* 资源处理器
* swagger会和freemarker的静态资源路径冲突因此需配置swagger的资源处理器
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
2.继承WebMvcConfigurationSupport
@Configuration
public class AppConfiguration extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CommonInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new UserInterceptor()).addPathPatterns("/manage/**").addPathPatterns("/user/logout");
super.addInterceptors(registry);
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
super.addResourceHandlers(registry);
}
}
特别注意:
重写
addInterceptors
的同时,addResourceHandlers
也要重写继承WebMvcConfigurationSupport会使Springboot中默认的
WebMvcAutoConfiguration
不初始化,WebMvc的配置需要自己手动实现,配置文件中的
spring.mvc.xxx
和spring.resources.xxx
也会不起作用不建议使用
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //WebMvcConfigurationSupport不存在才会实例化该类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
xxx
}
3.实现WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfig(){
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CommonInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new UserInterceptor()).addPathPatterns("/manage/**").addPathPatterns("/user/logout");
}
};
}
总结
springboot默认可以访问以下路径文件(见ResourceProperties):
classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources@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(默认配置)同时生效