上一篇文章我们介绍了过滤器Filter使用详解,Filter是依赖于Servlet,即Filter生命周期由Servlet容器管理,这篇文章我们介绍一个可以存在于Servlet以外的也具有过滤功能的组件——由Spring提供的Interceptor拦截器;拦截器主要应用在日志记录,权限校验等安全管理方面比较多
Spring Boot拦截器Intercepter详解
- 创建自定义拦截器实现HandlerInterceptor接口,并按照自定义要求重写指定方法
- 需要一个配置类实现WebMvcConfigurer 接口,并添加@Configuration注解
- 在配置类中重写addInterceptors方法,添加要拦截的url地址以及需要排除拦截的url地址
HandlerInterceptor接口源码:
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
根据源码可看出HandlerInterceptor接口提供了三个default方法,这三个方法的各有各的作用,用户想要自定义一个指定拦截规则的拦截器,需要重写其中一个或多个方法,这三个方法作用如下:
- preHandle: preHandle方法的作用是当请求在进入Controller之前拦截请求,对请求进行预处理,如登录验证或单点登录Cookie解析等都可以在此方法中执行,该方法由返回值,返回true表示放行进入到Controller层,false则请求结束返回错误相应信息
- postHandle: postHandle方法是在请求被Controller处理完但是还并未传递到网页模板进行渲染拦截,即Controller处理完,还返回ModelAndView之前执行该方法,可以操控ModelAndView的值;所以该方法多了一个参数ModelAndView对象,这个参数包含了Controller处理完后需要传递的Model参数,因此,我们可以在该方法通过ModelAndView对象对返回给前端的视图作一定的修改
- afterCompletion: afterCompletion方法作用就是做一些收尾工作,在ModelAndView返回给前端进行渲染后执行,比如有时候我们需要把每个线程的局部变量(如User信息)放入到ThreadLocal中,为了防止内存泄漏在最后需要清除ThreadLocal的内容,此操作就可以放在该方法中执行
再来看看WebMvcConfigurer源码:
public interface WebMvcConfigurer {
default void configurePathMatch(PathMatchConfigurer configurer) {
}
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
default void addFormatters(FormatterRegistry registry) {
}
default void addInterceptors(InterceptorRegistry registry) {
}
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
default void addCorsMappings(CorsRegistry registry) {
}
default void addViewControllers(ViewControllerRegistry registry) {
}
default void configureViewResolvers(ViewResolverRegistry registry) {
}
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
根据源码可看出WebMvcConfigurer提供了多个方法,并且也都是default方法,也是根据我们自定义配置重写其中一个或多个方法,这里就介绍以下两个比较常用的方法:
- addInterceptors: 从该方法名就可以了解到该方法是添加拦截器,即将拦截器交给IOC去执行,拦截器的需要拦截的路径以及需要排除拦截的路径在该方法中配置
- addResourceHandlers: 该方法的作用是配置静态资源路径,即某些请求需要读取某个路径下的静态资源内容,需要配置该静态资源的路径,通过该方法可以统一给这些请求配置指定静态资源路径
Intercepter应用实例
我们自定义一个获取并返回某个静态资源的内容以及整个请求所花费的时间拦截器
自定义的MyInterceptor:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(MyInterceptor.class);
private static final ThreadLocal<Long> START_THREAD_LOCAL = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String uri = request.getRequestURI();
LOGGER.info(uri + " preHandle");
Long startTime = System.currentTimeMillis(); //获取开始时间
START_THREAD_LOCAL.set(startTime); //线程绑定变量(该数据只有当前请求的线程可见)
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
String uri = request.getRequestURI();
LOGGER.info(uri + " postHandle");
Long startTime = START_THREAD_LOCAL.get();//得到线程绑定的局部变量(开始时间)
Long endTime = System.currentTimeMillis(); //2、结束时间
Long time = endTime - startTime;
LOGGER.info("http request all time: " + time + "ms");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
String uri = request.getRequestURI();
LOGGER.info(uri + " afterCompletion");
if (START_THREAD_LOCAL != null) {
START_THREAD_LOCAL.remove(); // 移除ThreadLocal中的局部变量
}
}
}
interceptor配置类:
import com.eureka.intercrpotor.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor()) // 添加拦截器
.addPathPatterns("/**") // 配置拦截请求url( ** 表示拦截所有请求url)
.excludePathPatterns("/hello"); // 排除某些不需要拦截的请求url(即带有/hello请求不会被拦截)
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**") // 配置需要添加静态资源的请求url
.addResourceLocations("classpath:/mydata/"); //配置静态资源路径
}
}
测试:
Controller类:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test/interceptor")
public ResponseEntity<String> testInterceptor() {
return ResponseEntity.ok("successful");
}
}
静态资源:
启动项目后访问 localhost:60011/test/interceptor:
控制台打印的日志:
我们再通过url访问静态资源请求 localhost:60011/test.jpg