项目里出现两个配置类继承WebMvcConfigurationSupport时,为什么只有一个会生效(源码分析)

  为什么我们的项目里出现两个配置类继承WebMvcConfigurationSupport时,只有一个会生效。我在网上找了半天都是说结果的,没有人分析源码到底是为啥,博主准备讲解一下,希望可以帮到大家!

  大家基本遇到过一种情况,就是我配置类中已经配置了,为什么就是没有生效呢?其中一种原因就是,自己写的配置类也继承了WebMvcConfigurationSupport,当项目出现两个配置类都继承该类时,只会讲第一个配置类生效,至于为什么,就是今天博主需要讲解的,我们必须了解一些springboot的bean的创建过程也就是其生命周期:

  SpringBoot的bean创建流程| ProcessOn免费在线作图,在线流程图,在线思维导图

  虽然画的比较简单,有许多细节都没有解析,但是对于当前我们的话题来讲已经基本可以了;

  第一步:我们的配置类是从哪里开始创建解析的:大家可以看到图示bean的流程中doProcessConfigurationClass(configClass, sourceClass, filter);方法,我们看一下是如何调用它 的:

 1  protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {2         if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {3             return;4         }5 6         ConfigurationClass existingClass = this.configurationClasses.get(configClass);7         if (existingClass != null) {8             if (configClass.isImported()) {9                 if (existingClass.isImported()) {
10                     existingClass.mergeImportedBy(configClass);
11                 }
12                 // Otherwise ignore new imported config class; existing non-imported class overrides it.
13                 return;
14             }
15             else {
16                 // Explicit bean definition found, probably replacing an import.
17                 // Let's remove the old one and go with the new one.
18                 this.configurationClasses.remove(configClass);
19                 this.knownSuperclasses.values().removeIf(configClass::equals);
20             }
21         }
22 
23         // Recursively process the configuration class and its superclass hierarchy.
24         SourceClass sourceClass = asSourceClass(configClass, filter);
25         do {
26             //从这里开始解析我们的当前配置类
27             sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
28         }
29         while (sourceClass != null);
30 
31         this.configurationClasses.put(configClass, configClass);
32     }

  这里可以看到一个while循环,为什么要这么设计呢?我们再看看doProcessConfigurationClass(configClass, sourceClass, filter);方法的源码

 1 protected final SourceClass doProcessConfigurationClass(2             ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)3             throws IOException {4 5         if (configClass.getMetadata().isAnnotated(Component.class.getName())) {6             // Recursively process any member (nested) classes first7             processMemberClasses(configClass, sourceClass, filter);8         }9 
10         // Process any @PropertySource annotations
11         for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
12                 sourceClass.getMetadata(), PropertySources.class,
13                 org.springframework.context.annotation.PropertySource.class)) {
14             if (this.environment instanceof ConfigurableEnvironment) {
15                 processPropertySource(propertySource);
16             }
17             else {
18                 logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
19                         "]. Reason: Environment must implement ConfigurableEnvironment");
20             }
21         }
22 
23         // Process any @ComponentScan annotations
24         Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
25                 sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
26         if (!componentScans.isEmpty() &&
27                 !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
28             for (AnnotationAttributes componentScan : componentScans) {
29                 // The config class is annotated with @ComponentScan -> perform the scan immediately
30                 Set<BeanDefinitionHolder> scannedBeanDefinitions =
31                         this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
32                 // Check the set of scanned definitions for any further config classes and parse recursively if needed
33                 for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
34                     BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
35                     if (bdCand == null) {
36                         bdCand = holder.getBeanDefinition();
37                     }
38                     if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
39                         parse(bdCand.getBeanClassName(), holder.getBeanName());
40                     }
41                 }
42             }
43         }
44 
45         // Process any @Import annotations
46         processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
47 
48         // Process any @ImportResource annotations
49         AnnotationAttributes importResource =
50                 AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
51         if (importResource != null) {
52             String[] resources = importResource.getStringArray("locations");
53             Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
54             for (String resource : resources) {
55                 String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
56                 configClass.addImportedResource(resolvedResource, readerClass);
57             }
58         }
59         //这里也很重要,这里开始会解析当前配置类里的bean,然后解析父类里面的bean,就是这里才会把WebMvcConfigurationSupport的所有bean
60         //都解析出来并添加到configClass里面,不管解析当前类还是父类,configClass都是自己当前的配置类,所以WebMvcConfigurationSupport
61         // Process individual @Bean methods
62         Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
63         for (MethodMetadata methodMetadata : beanMethods) {
64             configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
65         }
66 
67         // Process default methods on interfaces
68         processInterfaces(configClass, sourceClass);
69 
70         //最主要的就是这里,解析当前类的父类
71         // Process superclass, if any
72         if (sourceClass.getMetadata().hasSuperClass()) {
73             String superclass = sourceClass.getMetadata().getSuperClassName();
74             if (superclass != null && !superclass.startsWith("java") &&
75                     !this.knownSuperclasses.containsKey(superclass)) {
76                 //如果我们第一个继承了WebMvcConfigurationSupport的配置类,已经被扫描到,就会添加一个map缓存,
77                 //下一个也继承了WebMvcConfigurationSupport的配置类,将不在解析,直接返回null。结束循环,这也是外面一层为什么要添加while循环
78                 this.knownSuperclasses.put(superclass, configClass);
79                 // Superclass found, return its annotation metadata and recurse
80                 return sourceClass.getSuperClass();
81             }
82         }
83 
84         // No superclass -> processing is complete
85         return null;

  所以就现在来讲,基本已经决定了,解析第一个配置类的时候,第二个配置类重写的任何方法基本没什么用了,因为父类所有的bean已经在第一个配置类中解析扫描到了,就剩下如何去创建bean了。我们再继续往下看会更明白;

  第二步:现在当所有bean已经扫描到,并且bean定义已经完成,该开始实例化了,看一下createBeanInstance的创建过程,最后生成的时候会找到 factoryBean也就是我们自己的配置类

 1 private Object instantiate(String beanName, RootBeanDefinition mbd,2             @Nullable Object factoryBean, Method factoryMethod, Object[] args) {3 4         try {5             if (System.getSecurityManager() != null) {6                 return AccessController.doPrivileged((PrivilegedAction<Object>) () ->7                         this.beanFactory.getInstantiationStrategy().instantiate(8                                 mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args),9                         this.beanFactory.getAccessControlContext());
10             }
11             else {
12                 return this.beanFactory.getInstantiationStrategy().instantiate(
13                         mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args);
14             }
15         }
16         catch (Throwable ex) {
17             throw new BeanCreationException(mbd.getResourceDescription(), beanName,
18                     "Bean instantiation via factory method failed", ex);
19         }
20     }

  其中factoryBean就是我们的当前第一个被解析到的配置类bean,截图为证,我自己写了两个配置类,第一个被加载的是MyASD,瞎写的名,好区分,第二个配置类是WebConfiguration,我们只看WebMvcConfigurationSupport里面的其中一个bean的创建过程,就是requestMappingHandlerAdapter,为啥要看这个,正好跟上节json自定义衔接。

https://www.cnblogs.com/guoxiaoyu/p/13667961.html

 

   到这里,我们可以看到在生成requestMappingHandlerAdapter时,调用extendMessageConverters方法时,一定会调用第一个配置类中的重写方法,因为所有的WebMvcConfigurationSupport里面 bean都被第一个配置类解析完了,所有的factoryBean都是当前第一个配置类,就算第二个配置完没有报错,也不会生效了。

  我直接把这个问题用源码的方式讲解清楚,方便大家明白为什么配置两个WebMvcConfigurationSupport类,只有一个生效。

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

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

相关文章

SQL Server 2022从入门到精通

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

ardupilot开发 --- 视觉伺服 篇

风驰电掣云端飘&#xff0c;相机无法对上焦 1.视觉伺服分类2.视觉伺服中的坐标系3.成像模型推导4.IBVS理论推导5.IBVS面临的挑战6.visp 实践参考文献 1.视觉伺服分类 控制量是在图像空间中推导得到还是在欧式空间中推导得到&#xff0c;视觉伺服又可以分类为基于位置(PBVS)和基…

Flink 状态管理

一、状态 流式计算分为无状态和有状态两种情况。无状态的计算观察每个独立事件&#xff0c;并且根据最后一个事件输出结果。例如&#xff0c;流处理应用程序从传感器接收温度读数&#xff0c;并在温度超过90度时发出告警。有状态的计算则会基于多个事件输出结果。例如&#xf…

一款开源、高颜值的AI物联网数据平台

介绍 AIOT人工智能物联网平台是一站式物联网开发基础平台&#xff0c;帮助企业快速实现数字化、精细化数据管理。核心系统为&#xff1a;物联网平台 数据中台&#xff08;数据底座&#xff09; AI。 同时支持文生图、语音合成等。大模型支持陆续也会慢慢开发。 物联系统介绍…

CLIP 计算过程图解

CLIP 计算过程图解 CLIP模型是OpenAI开发的一种多模态学习模型&#xff0c;它通过学习文本和图像之间的关联&#xff0c;实现了跨模态的语义理解。下面是CLIP模型计算过程的简化描述&#xff1a; 1 数据准备 选取包含文本和图像对的mini-batch&#xff0c;例如"big tab…

【快速入门】Transformer: Attention Is All You Need

Transformer → \to → 【知名应用】BERT (unsupervised trained Transformer) Transformer &#xff1a;seq2Seq model with self-attention, 后续会主要说明 self-attentionTransformer的组成&#xff1a; Self-attention是 Attention变体&#xff0c;擅长捕获数据/特征的内…

完整代码Python爬取豆瓣电影详情数据

完整代码Python爬取豆瓣电影详情数据 引言 在数据科学和网络爬虫的世界里&#xff0c;豆瓣电影是一个丰富的数据源。在本文中&#xff0c;我们将探讨如何使用Python语言&#xff0c;结合requests和pyquery库来爬取豆瓣电影的详情页面数据。我们将通过一个具体的电影详情页面作…

oracle11.2.0.4 RAC 保姆级静默安装(一) GI集群软件

一、响应文件准备 我们直接使用软件解压后的response文件夹中的响应文件模板进行修改 选择当前服务器的主机名,产品目录是在已存在的/u01/app目录基础上自动创建的无需提前创建oraInventory 按需选择语言,具体语言配置参考表格 一般rac默认选择安装类型为CRS_CONFIG 对应正…

借助 NGINX Unit 在服务器端使用 WebAssembly

原文作者&#xff1a;Liam Crilly of F5 原文链接&#xff1a;借助 NGINX Unit 在服务器端使用 WebAssembly 转载来源&#xff1a;NGINX 中文官网 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.cn WebAssembly&#xff08;缩写为 Wasm&#xff09;可为 Web 应用领域提供…

C++ 教程 - 06 类的封装、继承、多态

文章目录 封装继承多态 封装 在private/protected 模块放置数据或者底层算法实现&#xff1b;在public块提供对外接口&#xff0c;实现相应的功能调用&#xff1b;类的封装案例 #include <iostream> using namespace std;// 类的定义 一般放在头文件 class Stu {public…

uniapp——上传图片获取到file对象而非临时地址——基础积累

最近在看uniapp的代码&#xff0c;遇到一个需求&#xff0c;就是要实现上传图片的功能 uniapp 官网地址&#xff1a;https://uniapp.dcloud.net.cn/ 上传图片有对应的API&#xff1a; uni.chooseImage方法&#xff1a;https://uniapp.dcloud.net.cn/api/media/image.html#choo…

没有手机怎么办呐!高考成绩出来了:请不要吹灭别人的灯——早读(逆天打工人爬取热门微信文章解读)

结婚的时候红包随礼随多少呢 引言Python 代码第一篇 洞见 高考成绩出来了&#xff1a;请不要吹灭别人的灯第二篇结尾 为什么是这个标题呢&#xff1f; 是因为摸鱼看足球直播 主播好兄弟结婚 他老婆问他要红包 引言 今天早上停电了 大概是在3点多的时候 我本身一直都没有开空调…

Javac编译器

Java语言的编译器是一段不确定的操作过程&#xff0c;可能是讲Java文件转变为class文件的过程&#xff0c;也可能是指虚拟机的后端编译&#xff0c;讲字节码转换为机器码的过程&#xff0c;还肯是静态提前编译器直接讲Java文件编译为本地机器代码的过程。 前端编译器&#xff…

HTML5五十六个民族网站模板源码

文章目录 1.设计来源高山族1.1 登录界面演示1.2 注册界面演示1.3 首页界面演示1.4 中国民族界面演示1.5 关于高山族界面演示1.6 联系我们界面演示 2.效果和源码2.1 动态效果2.2 源代码2.3 源码目录 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.ne…

数字化转型第三步:数字化业务创新与发展,提升收入和利润

引言&#xff1a;之前笔者的文章发布了企业数字化转型业务部分&#xff0c;如【开源节流】如何通过数字化转型增强盈利能力&#xff1f;企业供应链数字化转型如何做&#xff1f;让企业盈利能力增强再飞一会 【财务数字化转型之底座】集团企业财务数据中台系统建设方案 等文章&a…

基于jeecgboot-vue3的Flowable流程-自定义业务表单处理(二)-挂接自定义业务表单

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、增加一个根据服务名称动态寻找对应自定义表单组件的hooks import { ref, reactive, computed, markRaw, onMounted, defineAsyncComponent } from vue; import { listCustomForm } fro…

Redis之优惠券秒杀

文章目录 全局ID生成器添加优惠券实现优惠券秒杀下单超卖问题悲观锁和乐观锁相关文章乐观锁执行逻辑乐观锁解决超卖问题 一人一单功能超卖问题相关文章一人一单执行逻辑代码实现集群模式下锁失效 分布式锁基于Redis的分布式锁Redis实现分布式锁流程实现分布式锁初级版本分布式锁…

RabbitMQ(消息队列)

RabbitMQ 它是消息中间件&#xff0c;是在消息的传输过程中保存消息的容器&#xff0c;实现应用程序和应用程序之间通信的中间产品。目前主流消息队列通讯协议是AMQP&#xff08;二进制传输&#xff0c;支持多种语言&#xff09;、JMS&#xff08;HTTP传输&#xff0c;只支持J…

要离职了,记录一下个人在用的 Mac 应用

大家好&#xff0c;我是楷鹏。 通用 飞书 说起来不信&#xff0c;第一个推荐的是【飞书】&#xff0c;飞书是目前用过最舒服的项目管理应用了。 单拎出来一个飞书文档&#xff0c;功能和体验远超市面上腾讯文档、石墨文档、语雀等等。 现在飞书还支持个人版&#xff0c;No…

【系统架构设计师】六、信息系统基础知识(定义|分类|企业信息化系统|生命周期|建设原则|开发方法)

目录 一、信息系统的定义 二、信息系统的分类 三、企业使用的信息化系统 四、信息系统的生命周期 五、信息系统建设原则 六、信息系统的开发方法 6.1 结构化方法 6.2 原型法 6.3 构件化开发方法 6.4 面向服务的方法 6.5 面向对象的方法 6.6 敏捷方法 历年真题考情&#x…