关于Spring配置文件加载方式变化引发的异常详解


Posted in Java/Android onJanuary 18, 2022

问题背景

我们项目的配置文件一直是通过Apollo进行管理,但是近期由于某些特殊的部署需求,需要使用K8S的原生对象来获取配置,如此一来的话,就需要使用环境变量spring.config.location来指定application.properties文件的路径,以便动态的获取配置。

说明:项目是一个dubbo项目,配置文件中主要包括一些基础组件的配置、以及dubbo相关的配置。

这时候问题来了,在所有配置及代码都没有变化的情况下,如果不指定环境变量使用本地的application.properties,则没有异常任何,项目可以正常启动,但是一但通过spring.config.location 来加载配置,则项目会直接启动失败,并报如下异常:

关于Spring配置文件加载方式变化引发的异常详解

NoSuchBeanDefinitionException,这个异常前期误导我不少时间,它一般是Spring在容器初始化时,进行依赖注入的时候没有找到对应的bean定义,也就意味着这个bean压根没有被注册到BeanFactory中,这就很奇怪,只是配置文件的加载方式不同,为何会影响到bean的注册?

过程

找不到bean,最常见的问题有两种:要么是配置问题,比如扫描的包配置错误、配置未生效等。要么就是IoC容器的问题,存在多个容器,导致bean隔离。

定位

在这个问题场景下,两种原因都有可能,不过问题可以复现,就比较好解决。我们直接验证一下,最简单粗暴的法子就是断点伺候,对比两种配置加载方式方式的差异。我们知道除了延迟加载的bean之外,所有bean都是在org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons初始化的,那么在这个方法里以异常信息中的bean名称,打个条件断点看看

关于Spring配置文件加载方式变化引发的异常详解

通过断点,可以拿到两个信息

1、通过当前的堆栈,可以看到当前的初始化bean逻辑并不是SpringBoot的IoC容器触发的,而是SpringCloud

关于Spring配置文件加载方式变化引发的异常详解

2、beanNames,也就是当前beanFactory中所有已注册的bean中,没有加载到任何通过Spring的@Service注解标识的bean,但是却加载到了所有被Dubbo的@Service加载到的bean。

这样一来我们可以确定的是确实存在多容器隔离,SpringCloud也会通过BootstrapApplicationListener这个监听器创建一个IoC容器,查看官方说明:

A Spring Cloud application operates by creating a “bootstrap” context, which is a parent context for the main application. It is responsible for loading configuration properties from the external sources and for decrypting properties in the local external configuration files. The two contexts share an Environment, which is the source of external properties for any Spring application. By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence, so they cannot be overridden by local configuration.

SpringCloud创建的容器的加载顺序比SpringBoot要早,是它的父容器,并且它们共享同一个Environment。

虽然现在知道了异常产生的原因,但是为什么换了配置加载方式就会由父容器加载?根据上面的第二个信息,被Dubbo注解标识的bean都被加载了,但是这些bean依赖的SpringBean还没有加载进来,这意味着由于配置文件加载方式的变化,导致Dubbo标记的bean加载时机发生的改变。

根因

那接下来就是看一下Dubbo的bean加载逻辑,我们的服务比较老了,使用的spring-boot-starter-dubbo来整合SpringBoot与Dubbo。一般spring-boot-starter都是通过@EnableXXX或spring.factories来自动装载相关的bean,而spring-boot-starter-dubbo没有使用@Enable,那直接找到jar包下的spring.factories文件,找到对应的Initializer:

关于Spring配置文件加载方式变化引发的异常详解

它实现的是ApplicationContextInitiailizer,这些接口会在准备完Context环境,在prepareContext中调用,那么很明显,父容器肯定先会执行,子容器后执行。 看代码逻辑,它只有在读取到spring.dubbo.scan有值时,才会去注册bean,到这里原因已经比较明显了,使用application.properties时父容器读不到配置,而使用spring.config.location加载配置时,父容器可以读到配置。

配置加载顺序

上面提到的SpringCloud文档中有这么一句话:

By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence

那么通过spring.config.location方式加载属性是不是在bootstrap phase中呢,直接找到SpringCloud的加载类BootstrapApplicationListener,搜索spring.config.location,发现它确实优先加载

关于Spring配置文件加载方式变化引发的异常详解

而如果是使用application.properties,那么配置文件则不会被SpringCloud加载到,会由子容器加载。

解决

问题根因找到了,想解决就比较简单了,两种方式:

  • 直接关闭SpringCloud的boostrap listener,通过配置spring.cloud.bootstrap.enabled=false即可
  • 这个问题其实也是dubbo的整合方式不合理导致的,使用Dubbo自带的注解扫描,不使用配置文件的方式

问题其实比较简单,但是挺有意思,分享一下过程与思路*~*

到此这篇关于Spring配置文件加载方式变化引发的异常的文章就介绍到这了,更多相关Spring配置文件加载方式内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Java/Android 相关文章推荐
SpringBoot集成Redis,并自定义对象序列化操作
Jun 22 Java/Android
java解析XML详解
Jul 09 Java/Android
SpringRetry重试框架的具体使用
Jul 25 Java/Android
关于springboot配置druid数据源不生效问题(踩坑记)
Sep 25 Java/Android
Jpa Specification如何实现and和or同时使用查询
Nov 23 Java/Android
Mybatis是这样防止sql注入的
Dec 06 Java/Android
深入浅出讲解Java8函数式编程
Jan 18 Java/Android
Mybatis-Plus进阶分页与乐观锁插件及通用枚举和多数据源详解
Mar 21 Java/Android
Netty分布式客户端接入流程初始化源码分析
Mar 25 Java/Android
java实现面板之间切换功能
Jun 10 Java/Android
Java 多线程并发FutureTask
Jun 28 Java/Android
Java 中的 Lambda List 转 Map 的多种方法详解
Jul 07 Java/Android
springboot中的pom文件 project报错问题
Jan 18 #Java/Android
java代码实现空间切割
springboot新建项目pom.xml文件第一行报错的解决
Jan 18 #Java/Android
关于@OnetoMany关系映射的排序问题,使用注解@OrderBy
Dec 06 #Java/Android
SpringBoot整合阿里云视频点播的过程详解
Dec 06 #Java/Android
SpringDataJPA在Entity中常用的注解介绍
Dec 06 #Java/Android
Java中try catch处理异常示例
Dec 06 #Java/Android
You might like
php代码收集表单内容并写入文件的代码
2012/01/29 PHP
PHP分页效率终结版(推荐)
2013/07/01 PHP
ThinkPHP学习笔记(一)ThinkPHP部署
2014/06/22 PHP
php递归实现无限分类的方法
2015/07/28 PHP
示例详解Laravel重置密码代码重构
2016/08/10 PHP
Laravel 5.4.36中session没有保存成功问题的解决
2018/02/19 PHP
js对数字的格式化使用说明
2011/01/12 Javascript
js实时监听文本框状态的方法
2011/04/26 Javascript
JS实现字体选色板实例代码
2013/11/20 Javascript
Tab切换组件(选项卡功能)实例代码
2013/11/21 Javascript
js统计页面的来访次数实现代码
2014/05/09 Javascript
javascript运算符语法全面概述
2016/07/14 Javascript
jQuery实现表格文本框淡入更改值后淡出效果
2016/09/27 Javascript
vue项目中的webpack-dev-sever配置方法
2017/12/14 Javascript
原生JS实现前端本地文件上传
2018/09/08 Javascript
jquery选择器和属性对象的操作实例分析
2020/01/10 jQuery
vue+render+jsx实现可编辑动态多级表头table的实例代码
2020/04/01 Javascript
vue双击事件2.0事件监听(点击-双击-鼠标事件)和事件修饰符操作
2020/07/27 Javascript
用Python实现服务器中只重载被修改的进程的方法
2015/04/30 Python
python抽取指定url页面的title方法
2018/05/11 Python
pytorch: tensor类型的构建与相互转换实例
2018/07/26 Python
手把手教你使用Python创建微信机器人
2019/04/29 Python
利用Tensorflow构建和训练自己的CNN来做简单的验证码识别方式
2020/01/20 Python
Python几种常见算法汇总
2020/06/02 Python
css3+伪元素实现鼠标移入时下划线向两边展开的效果
2017/04/25 HTML / CSS
详解三种方式实现平滑滚动页面到顶部的功能
2019/04/23 HTML / CSS
英国赛车、汽车改装和摩托车零件购物网站:Demon Tweeks
2018/10/29 全球购物
Quiksilver荷兰官方网站:冲浪和滑雪板
2019/11/16 全球购物
澳大利亚领先的时尚内衣零售商:Bras N Things
2020/07/28 全球购物
制定岗位职责的原则
2013/11/08 职场文书
幼儿园区域活动总结
2014/05/08 职场文书
教育项目合作协议书格式
2014/10/17 职场文书
赡养老人协议书范本
2015/08/06 职场文书
生日宴会家属答谢词
2015/09/29 职场文书
python使用pywinauto驱动微信客户端实现公众号爬虫
2021/05/19 Python
单机多实例部署 MySQL8.0.20
2022/05/15 MySQL