一、拦截器加载原理
拦截器是在容器启动时,就创建并加载好,此时并未放入拦截器链中,只是放在一个拦截器集合当中,当一个请求进来之后,会通过匹配路径,查看是否有命中集合中的拦截器的拦截路径,如果命中,则放入拦截器链中,如果没有命中则不会放入。之后在执行请求之前,会循环遍历执行命中的拦截器中的逻辑,如果都通过,则执行请求,反之则不执行。
一、拦截器加载进拦截器集合
默认情况下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这个类中。
@Overrideprotected 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;@Datapublic 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 {@Resourceprivate InterceptorPro interceptorPro;@Overridepublic 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 {@Resourceprivate InterceptorPro interceptorPro;@Overridepublic 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 {@Resourceprivate ApplicationContext applicationContext;@Resourceprivate InterceptorPro interceptorPro;@Overridepublic 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]));}}}
第二种排序方式: