一、拦截器加载原理
拦截器是在容器启动时,就创建并加载好,此时并未放入拦截器链中,只是放在一个拦截器集合当中,当一个请求进来之后,会通过匹配路径,查看是否有命中集合中的拦截器的拦截路径,如果命中,则放入拦截器链中,如果没有命中则不会放入。之后在执行请求之前,会循环遍历执行命中的拦截器中的逻辑,如果都通过,则执行请求,反之则不执行。
一、拦截器加载进拦截器集合
默认情况下DispatcherServlet会注册3个HandlerMapping,分别是BeanNameUrlHandlerMapping、RequestMappingHandlerMapping以及RouterFunctionMapping。
其中HandlerMapping的作用是存储请求路径与处理方法的映射,收到请求后就能通过HandlerMapping找到对应的处理方法RequestMappingHandlerMapping对象处理通过Controller注解的类中RequestMapping注解标注的方法。
这个对象主要是在WebMvcConfigurationSupport这个配置类里面做了Bean的创建,这个配置类,主要是对springMVC所有配置做初始化。会在项目启动时自动纳入容器管理,并初始化。
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// 创建一个RequestMappingHandlerMapping对象,由于这个对象继承了AbstractHandlerMapping这个对象,会先去创建AbstractHandlerMapping这个对象,再创建创建一个RequestMappingHandlerMapping对象
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
// 添加拦截器
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
// WebMvcConfigurationSupport类里面的方法
protected final Object[] getInterceptors(
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
// 加载开始时是为空的
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
// 添加拦截器,这个方法是一个空方法,有 WebMvcConfigurationSupport的子类实现,这里会进入DelegatingWebMvcConfiguration这个类
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
// DelegatingWebMvcConfiguration这个类的添加拦截器方法
protected void addInterceptors(InterceptorRegistry registry) {
// 从这进入会到WebMvcConfigurerComposite类的方法
this.configurers.addInterceptors(registry);
}
// WebMvcConfigurerComposite的addInterceptors方法
public void addInterceptors(InterceptorRegistry registry) {
// 这里循环遍历的delegates是当前类创建的一个private final List<WebMvcConfigurer> delegates = new ArrayList<>();集合,
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
}
}
// WebMvcConfigurerComposite这个类里面有一个方法对这个集合进行设置值
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
// 这个设置值的方法又是被DelegatingWebMvcConfiguration这个类调用了,这个类里面有这个方法,再去点击会发现没有地方调用了,
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
// 点击DelegatingWebMvcConfiguration这个类会发现是由EnableWebMvc这个注解将这个类导入进容器中,除此之外,springboot中的WebMvcAutoConfiguration类中
// EnableWebMvcConfiguration这个内部类继承了DelegatingWebMvcConfiguration这个类,然后注入了容器中
/**那也就是说当创建EnableWebMvcConfiguration这个类对象时,容器会先创建DelegatingWebMvcConfiguration对象,然后扫描实现了这个接口的WebMvcConfigurer类,然后设置进
**private final List<WebMvcConfigurer> delegates = new ArrayList<>();集合中
**
*/
// 注解EnableWebMvc
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
// EnableWebMvcConfiguration这个内部类
@Configuration(
proxyBeanMethods = false
)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
}
添加完拦截器后还是会回到WebMvcConfigurationSupport这个类中的getInterceptors
这个方法里继续执行 剩余逻辑。
下面代码就是InterceptorRegistry类对外提供获取拦截器集合里所有拦截器的方法
protected List<Object> getInterceptors() {
return this.registrations.stream()
.sorted(INTERCEPTOR_ORDER_COMPARATOR)
.map(InterceptorRegistration::getInterceptor)
.collect(Collectors.toList());
}
然后将获取到的所有拦截器放到AbstractHandlerMapping这个抽象类中的
private final List<Object> interceptors = new ArrayList<>();这个集合当中。
之后将继续执行剩下的逻辑
之后会执行
RequestMappingHandlerMapping
的拦截器初始化方法initInterceptors,这个方法是在他的父类
AbstractHandlerMapping这个类中。
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
// 这里就是初始化拦截器
initInterceptors();
}
// 还是在当前类下
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
这样做的目的是为了之后请求进来了,将拦截器添加到拦截器链中
二、拦截器加载进拦截器链
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 如果handler不是handler执行链对象,就创建一个
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 获取请求进来的路径,这里一定要注意server:servlet:context-path: /work 这个在yaml配置的路径,不会被截取到。
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
// 循环遍历这个就是之前挡在AbstractHandlerMapping这个类里面的adaptedInterceptors集合中的拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
// 关键在这里,如果匹配上了,才会添加进拦截器链中等待执行
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
原始请求:/work/work/test/export,才能命中拦截器路径,这是由于系统配置多加了一个/work
server: servlet: context-path: /work
二、实现多拦截器链式执行,确保每个拦截器按顺序执行且支持动态添加或移除拦截器
想要动态添加和移除拦截器可以借用nacos来进行控制。由于spring并未提供直接从拦截器链中或者拦截器集合中移除的方法,所以可以使用控制拦截器是否执行拦截器逻辑的方式,变相达到动态移除和添加拦截器的效果
一、nacos配置
test:
dynamic-interceptors: [
{
"name": "FirstInterceptor",
"enabled": true, // 控制是否添加进容器
"valid": false, // 控制是否开启拦截器
"order": 2, // 控制排序
"includePatterns": ["/work/test/**"],
"excludePatterns": ["/work/demo/**"] // 白名单
},
{
"name": "SecondInterceptor",
"enabled": true,
"valid": false,
"order": 1,
"includePatterns": ["/work/**"],
"excludePatterns": []
}
]
二、获取nacos配置
// 可动态获取最新的nacos配置
@Data
@Configuration
@ConfigurationProperties("test")
public class InterceptorPro {
// 拦截器信息
private List<DynamicInterceptors> dynamicInterceptors;
@Data
public static class DynamicInterceptors {
private String name;
private boolean enabled;
private boolean valid;
private Integer order;
private List<String> includePatterns;
private List<String> excludePatterns;
}
}
三、提前创建好的拦截器,需要实现自己的拦截逻辑
@Slf4j
@Component("FirstInterceptor")
public class FirstInterceptor implements HandlerInterceptor {
@Resource
private InterceptorPro interceptorPro;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("开始进入拦截器:{}","FirstInterceptor");
for (InterceptorPro.DynamicInterceptors dynamicInterceptors : interceptorPro.getDynamicInterceptors()) {
if(this.getClass().getSimpleName().equals(dynamicInterceptors.getName()) && Boolean.FALSE == dynamicInterceptors.isValid()){
log.info("当前拦截器未开启|拦截器名:{}",dynamicInterceptors.getName());
return Boolean.TRUE;
}
}
String channel = request.getHeader("channel");
if (!"test".equalsIgnoreCase(channel)) {
log.info("channel不等于test,禁止通过:{}","FirstInterceptor");
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}
@Slf4j
@Component("SecondInterceptor")
public class SecondInterceptor implements HandlerInterceptor {
@Resource
private InterceptorPro interceptorPro;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("开始进入拦截器:{}","SecondInterceptor");
for (InterceptorPro.DynamicInterceptors dynamicInterceptors : interceptorPro.getDynamicInterceptors()) {
if(this.getClass().getSimpleName().equals(dynamicInterceptors.getName()) && Boolean.FALSE == dynamicInterceptors.isValid()){
log.info("当前拦截器未开启|拦截器名:{}",dynamicInterceptors.getName());
return Boolean.TRUE;
}
}
String channel = request.getHeader("channel");
if (!"test".equalsIgnoreCase(channel)){
log.info("channel不等于test,禁止通过:{}","SecondInterceptor");
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}
四、添加添加器到拦截器集合当中
@Component
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Resource
private ApplicationContext applicationContext;
@Resource
private InterceptorPro interceptorPro;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 这里按照nacos配置好的顺序由低到高进行排序,拦截器的顺序是按照添加拦截器的先后,由高到低进行排序的,越先添加的拦截器优先级越高
List<InterceptorPro.DynamicInterceptors> interceptors = interceptorPro.getDynamicInterceptors().stream().filter(Objects::nonNull)
.sorted(Comparator.comparing(InterceptorPro.DynamicInterceptors::getOrder))
.collect(Collectors.toList());
// 遍历nacos配置
for (InterceptorPro.DynamicInterceptors config : interceptors) {
// 通过nacos配置好的拦截器名字,从容器中获取拦截器对象
HandlerInterceptor interceptor = (HandlerInterceptor) applicationContext.getBean(config.getName());
// 如果enabled配置开启,则添加进拦截器集合当中,注意这里并没有添加到拦截器链中
if (config.isEnabled()) {
registry.addInterceptor(interceptor)
.addPathPatterns(config.getIncludePatterns().toArray(new String[0]))
.excludePathPatterns(config.getExcludePatterns().toArray(new String[0]));
}
}
}
第二种排序方式: