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

news2024/12/23 13:21:49

一、拦截器加载原理

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

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

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

 

 

第二种排序方式:

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2257812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高通QCA-WiFi-10.4驱动源码解析文档:无线驱动开发的利器

高通QCA-WiFi-10.4驱动源码解析文档&#xff1a;无线驱动开发的利器 【下载地址】高通QCA-WiFi-10.4驱动源码解析文档分享 本仓库提供了一份高通最新的QCA-WiFi-10.4驱动源码解析文档&#xff0c;该文档对于无线驱动开发人员来说&#xff0c;是一份非常宝贵的帮助资料。通过这份…

数据结构与算法 五大算法

文章目录 1&#xff0c;时间复杂度与空间复杂度 2&#xff0c;插入排序 3&#xff0c;希尔排序 4&#xff0c;选择排序 1&#xff0c;单趟排序 2&#xff0c;选择排序PLUS版本 5&#xff0c;冒泡排序 6&#xff0c;快速排序 1&#xff0c;hoare版本 2&#xff0c;挖坑法 前言 …

数据链路层总结

- - 链路、物理链路&#xff1a;两节点间物理线路&#xff08;有线、无线&#xff09;&#xff0c;中间没有任何其他的交换节点 数据链路、逻辑链路&#xff1a; 链路 协议需要的硬件、软件 网络适配器(网卡)&#xff1a;包含物理层、数据链路层 网络适配器软件驱动程…

入门pytorch-Transformer

前言 虽然Transformer是2017年由Google推出&#xff0c;如果按照读论文只读近两年的思路看&#xff0c;那它无疑是过时的&#xff0c;但可惜的是&#xff0c;目前很多论文的核心依然是Transformer&#xff0c;或者由其进行改进的&#xff0c;故本文使用pytorch来搭建一下Trans…

PHP中GD库的使用

由于我要用到php的验证码 <?php session_start();// 生成验证码 $random_code substr(md5(uniqid(mt_rand(), true)), 0, 6);// 将验证码保存到 session 中 $_SESSION[captcha] $random_code;// 创建图片 $font 6; $image_width 100; $image_height 40;// 创建图像 $…

【OpenCV】图像转换

理论 傅立叶变换用于分析各种滤波器的频率特性。对于图像&#xff0c;使用 2D离散傅里叶变换&#xff08;DFT&#xff09; 查找频域。快速算法称为 快速傅立叶变换&#xff08;FFT&#xff09; 用于计算DFT。 Numpy中的傅立叶变换 首先&#xff0c;我们将看到如何使用Numpy查…

ThingsBoard规则链节点:RabbitMQ 节点详解

ThingsBoard 是一个开源的物联网平台&#xff0c;允许开发者快速构建IoT产品。它提供了设备连接、数据收集、处理和可视化等功能。为了实现高效的数据处理和消息传递&#xff0c;ThingsBoard 集成了多种消息队列服务&#xff0c;其中就包括了RabbitMQ。 RabbitMQ 是一个广泛使用…

健康管理系统(Koa+Vue3)

系统界面(源码末尾获取) 系统技术 Vue3 Koa Nodejs Html Css Js ....... 系统介绍 系统比较简单,轻轻松松面对结业课堂作业.采用的是基于nodejs开发的Koa框架作为后端,采用Vue框架作为前端,完成快速开发和界面展示. 系统获取 啊啊啊宝/KoaVue3https://gitee.com/ah-ah-b…

Muduo 网络库 入门详解

文章目录 1. 什么是 Muduo 网络库&#xff1f;2. Muduo 的核心架构2.1 EventLoop2.2 Channel2.3 Poller2.4 TimerQueue2.5 TcpServer 和 TcpConnection架构图 3. Muduo 的工作原理4. 部分组件介绍4.1 ProtobufCodec4.2 ProtobufDispatcher4.3 muduo::net::EventLoop4.4 muduo::…

Scratch游戏推荐 | 磁铁与磁场原理模型——探索科学的奥秘!

今天为大家推荐一款既有趣又富有教育意义的Scratch互动作品——《磁铁与磁场原理模型》&#xff01;由ps49student503-25制作&#xff0c;这款作品通过直观的方式展示了磁铁和磁场的相互作用&#xff0c;帮助玩家深入了解磁场的方向与强度。快来拖动磁铁&#xff0c;观察磁场如…

汽车总线协议分析-CAN总线

随着汽车工业的发展&#xff0c;汽车各系统的控制逐步向自动化和智能化转变&#xff0c;汽车电气系统变得日益复杂。许多车辆设计使用CAN、CAN-FD、LIN、FlexRay或SENT在电子控制单元(ECU)之间以及ECU与传感器&#xff0c;执行器和显示器之间进行通信。这些ECU之间的通信允许车…

十四、Pod的升级和回滚

当集群中的某个服务需要升级时,我们需要停止目前与该服务相关的所有Pod,然后下载新版本镜像并创建新的Pod。如果集群规模比较大,则这个工作变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用。Kubernetes提供了滚动升级功能来解决上述问题。 如…

Redis篇-1--入门介绍

1、Redis概述 ‌Redis&#xff08;Remote Dictionary Server&#xff09;&#xff0c;全称为远程字典服务。‌是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库。 Redis提供了多种数据类型的存储&#xff0c;来适应不同场景下的存储需…

游戏引擎学习第35天

开场介绍 今天的任务是继续改进一个虚拟的瓦片地图系统&#xff0c;使其适合处理更大的世界。我们希望这个系统能管理大范围的游戏世界&#xff0c;其中包含按需存储的小区域。昨天&#xff0c;我们介绍了“内存区域”的概念&#xff0c;用于管理持久性存储。我们计划今天继续…

Apache Echarts和POI

目录 Apache ECharts 介绍 入门 绘制一个简单的图表 Apache POI 介绍 通过POI创建Excel文件并且写入文件内容 通过POI读取Excel文件中的内容 导出Excel表格 Apache ECharts 介绍 Apache ECharts 是一款基于 Javascript 的数据可视化图表库&#xff0c;提供直观&#xf…

怎么配置点击deploy就能把jar包直接打到nexus私库上,以及怎么配置从私库下载jar包

一.怎么配置点击deploy就能把jar包直接打到nexus私库上 方式一:在pom文件配置私库地址 1.第一步&#xff0c;在pom文件配置仓库地址&#xff0c;用于 deploy 上传 releases 对应正式版的仓库 snapshots 对应快照版的仓库 如果你打的jar包是以 -SNAPSHOT 结尾的, 那么就会…

基于最新的Apache StreamPark搭建指南

一、StreamPark 的介绍 官方文档:Apache StreamPark (incubating) | Apache StreamPark (incubating) 中文文档:Apache StreamPark (incubating) | Apache StreamPark (incubating)Github地址:https://github.com/apache/incubator-streampark Apache StreamPark™ 是一个…

数字IC后端实现常见的physical only cell都有哪些?如何添加这些cell?

数字IC后端实现阶段常见功能cell有哪些&#xff1f;比如AND&#xff0c;AOI&#xff0c;NAND等。 physical cell有哪些&#xff1f;都是干什么用的&#xff1f; 数字后端零基础入门系列 | Innovus零基础LAB学习Day9 &#xff08;1&#xff09; well tap cells&#xff1a;防止…

Promise详解-1:初识Promise

最近在回顾ES6的知识&#xff0c;想整理下跟Promise相关的内容。我准备整一个Promise解读的系列&#xff0c;看看能深入到什么程度吧。由浅入深&#xff0c;先认识下Promise。 痛苦的回忆&#xff1a;回调地狱 假如现在让你维护一个“古老”的项目&#xff0c;缺少脚手架的加…

【css】基础(一)

本专栏内容为&#xff1a;前端专栏 记录学习前端&#xff0c;分为若干个子专栏&#xff0c;html js css vue等 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;css专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &a…