springboot系列--拦截器加载原理

news/2024/12/11 16:28:36/

一、拦截器加载原理

        拦截器是在容器启动时,就创建并加载好,此时并未放入拦截器链中,只是放在一个拦截器集合当中,当一个请求进来之后,会通过匹配路径,查看是否有命中集合中的拦截器的拦截路径,如果命中,则放入拦截器链中,如果没有命中则不会放入。之后在执行请求之前,会循环遍历执行命中的拦截器中的逻辑,如果都通过,则执行请求,反之则不执行。

一、拦截器加载进拦截器集合

        默认情况下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]));}}}

 

 

第二种排序方式:

 

 

 


http://www.ppmy.cn/news/1554268.html

相关文章

安全架构评审

安全架构评审 1.概述2.安全设计原则3.美团安全架构评审模型安全需求分析架构review攻击面分析和威胁建模攻击面分析威胁列表 1.概述 完整的安全评审会包含安全架构评审、安全代码审核和安全测试三个手段 安全架构评审聚焦于探寻安全设计中的漏洞&#xff0c;以宏观视野全面考…

IoTDB Allocate WAL Buffer Fail Because out of memory

问题及现象 时序数据库 IoTDB 集群报错&#xff1a; The write is rejected because the wal directory size has reached the threshold 53687091200 bytes. You may need to adjust the flush policy of the storage storageengine or the IoTConsensus synchronization pa…

鼠标右键单击Git Bash here不可用

最近在学习git时突然发现右键的git bash没反应&#xff0c;但是去点击应用图标就能正常运行&#xff0c;通常是因为你在安装git之后改变了它的目录名称或者位置&#xff0c;我就是因为安装后改变了一个文件夹的文件名导致不可用 在安装git时系统会默认给鼠标右键选项的git Bas…

菜鸟每日刷牛客HJ2

菜鸟每日刷牛客 HJ2 计算某字符出现次数 描述 写出一个程序&#xff0c;接受一个由字母、数字和空格组成的字符串&#xff0c;和一个字符&#xff0c;然后输出输入字符串中该字符的出现次数。&#xff08;不区分大小写字母&#xff09; 数据范围&#xff1a; 1≤n≤1000 输…

word poi-tl 表格功能增强,实现表格功能垂直合并

目录 问题解决问题poi-tl介绍 功能实现引入依赖模版代码效果图 附加&#xff08;插件实现&#xff09;MergeColumnData 对象MergeGroupData 类ServerMergeTableData 数据信息ServerMergeTablePolicy 合并插件 问题 由于在开发功能需求中&#xff0c;word文档需要垂直合并表格&…

【力扣】155. 最小栈(Java版)

文章目录 1. 题目2. 题目分析3. 代码示例 1. 题目 在线OJ 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素val推入堆栈。 void pop()…

硬件设计-TINA新建元器件仿真模型

目录 简介: 项目内容: 过程详情: 问题处理: 简介: 有时候,需要一些其他公司的元器件来进行仿真,这就需要使用到TINA的新建宏向导。 TINA是很好用的原理图仿真软件,先是绘制原理图,然后进行电路原理的仿真,如果原理图仿真,电路功能符合设计要求,就可以接下来把…

本地无需公网可访问开源趣味艺术画板 paint-board

paint-board 一款用于绘画或涂鸦的工具&#xff0c;它非常轻量而且很有趣&#xff0c;集成了多种创意画笔和绘画功能&#xff0c;能够支持形状绘制、橡皮擦、自定义画板等操作&#xff0c;并可以将作品保存为图片。 第一步&#xff0c;本地部署安装 paint-board 1&#xff0c…