当请求来到 DispatcherServlet 时, 它会根据 HandlerMapping 的机制找到处理器, 这样就会返回一个 HandlerExecutionChain 对象,这个对象包含处理器和拦截器。这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。
首先所有的拦截器都需要实现 HandlerInterceptor 接口,该接口源码定义如下
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
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 {
}
}
注意:上述源码是Spring Boot2.x,该版本要求JDK1.8+。也正因为这个接口是 Java 8 的接口,所以 3 个方法都被声明为 default, 并且提供了空实现。当我们需要自己定义方法的时候, 只需要实现 HandlerInterceptor, 覆盖其对应的方法即可。
学习了拦截器的执行流程之后,就要开始学习拦截器的开发规范了,其实要在项目中使用它,也是非常的简单,开发者只需要两步就可以在项目中灵活的使用了。
实现 Handlerlnterceptor 接口,然后按照自己的需要重写拦截器方法。
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("请求路径uri:{}", request.getRequestURI());
String token = request.getHeader(Constant.TOKEN);
if (StringUtils.isNotBlank(token) && Objects.isNull(request.getSession().getAttribute(token))) {
return false;
}
return true;
}
}
有了拦截器, Spring MVC 并不会发现它,它还需要进行注册才能够拦截处理器。
拦截器的注册有两种方式,无论是哪种方式,都是覆盖其 addInterceptors
方法。
WebMvcConfigurer
接口WebMvcConfigurationSupport
类WebMvcConfigurationSupport
后会导致自动配置【templates和static】失效,所以用了这种方式后有需要的话【项目中还有前端页面和资源的前提下】还要指定页面和静态资源的位置。即需要重写addResourceHandlers
方法,对于现在前后端分离的项目,这就是无所谓的事情。WebMvcConfigurationSupport
类后静态资源配置失效的原因这部分的学习需要具备一定的Spring Boot源码分析能力,没学习过的朋友可以先参考楼主这篇文章了解一下:Spring Boot源码分析
分析如下:
在spring-boot-autoconfigure-2.1.10.RELEASE.jar
的spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
里配置有WebMvcAutoConfiguration
类
该类定义如下
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
划重点@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
在这里因为“继承”的特性,该项目类路径中缺少WebMvcConfigurationSupport
类型的bean时该自动配置类才会生效,当我们继承了 WebMvcConfigurationSupport
时,该类就不会被扫描到了,因此默认的配置就生效了!
无论使用上述哪种规则,拦截器的配置都是在addInterceptors
方法内完成,具体规则如下
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
super.addInterceptors(registry);
}
对于某些静态资源我们并不希望它被拦截,此时可以重写addResourceHandlers
方法,当然前后端分离的项目【前端一个项目,后端一个项目】,就不需要配置了。
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
registry.addResourceHandler("/webjars/**")
// 注意如下的映射资源路径,不能写/**
.addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
多个拦截器的执行结果是责任链模式的规则,对于处理器前方法采用先注册先执行,而处理器后方法和完成方法则是先注册后执行的规则。是不是和AOP很相似,楼主对这种规则有一个更简单直观的理解【脑补一个同心圆】。
当拦截器链内存在多个拦截器时,处理器前(preHandle)方法会执行,但是一旦有一个返回 false,则后续的拦截器、 处理器和所有拦截器的处理器后(postHandle) 方法都不会被执行。而完成方法 afterCompletion 则不一样,它只会执行返回 true 的拦截器的完成方法,而且顺序是先注册后执行。
最后,没有案例验证的博文扯再多都是耍流氓,案例呈上:Spring Boot【customize-auth子项目】