spring cloud项目中,在bootstrap.yml中指定了active的profile,结果不生效

问题描述

接手的一个spring cloud项目,我最近在对其进行改造,更符合自己的习惯。我个人习惯在bootsrap.yml中指定spring.profiles.active,如下所示,只要修改spring.profiles.active的值,就能切换到想要的profile去,使用对应的nacos配置:

yaml 复制代码
spring:
  application:
    name: demo-svc
  profiles:
    active: dev


---
spring:
  config:
    activate:
      on-profile: dev
  cloud:
    nacos:
      config:
        username: xxx
        password: xxxx
        enabled: false
        file-extension: yaml #文件扩展名
        server-addr: 1.1.1.1:8848
        namespace: 148586df-f999-4f83-a2fe-39b9add32196
---
spring:
  config:
    activate:
      on-profile: test
  cloud:
    nacos:
      config:
        username: xxx
        password: xxxx
        enabled: true
        file-extension: yaml #文件扩展名
        server-addr: 1.1.1.1:8848
        namespace: dfcf830b-0cd3-433b-bf35-6d153b9f142d

当然,上面文件中的profile为dev,我们还可以在命令行启动脚本中指定("java -Dspring.profiles.active=prod -jar demo.jar"),命令行中优先级最高,此时profile就是prod。

当然,命令行中不指定时,就应该是bootsrap.yml中的为准。

我这次问题就是发生在,命令行未指定的情况下,bootstrap.yml中指定了dev这个profile。

下图,我这里并未启用nacos,此时按理说就会去找application-dev.yml这个配置:

最终,却好像是没去读取application-dev.yml,导致程序读取不到配置,直接启动失败了。

但是,我发现,服务器上运行时,我们都是在命令行中指定了profile,运行是完全正常的:

复制代码
"java -Dspring.profiles.active=dev -jar demo.jar"

为啥不在命令行中指定就不行呢(在本地idea中启动,我都是不会加命令行参数的,按理说就会取bootstrap.yml中的profile)?

框架版本,spring-boot基本是2.7.*中的最高版本了,cloud版本是2021.0.5。

shell 复制代码
<properties>
    <java.version>1.8</java.version>
    <spring-boot.version>2.7.16</spring-boot.version>
    <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
    <spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>

简略排查过程

正常和异常情况下的对比

由于可以复现,且在本地可以debug,而且可以对比,正常和异常时的情况,这个问题肯定是能找到的。

我发现,正常情况下(本地idea启动时指定下命令行参数),在查找配置值时,一般就是去Environment中的propertySource列表中查找,正常长这样:

上图所示,是有一个classpath下的resource:application-dev.yml。这个propertySource的类型是com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableMapPropertySourceWrapper,大家可能有点奇怪,不熟悉这个类,这个是因为我们项目在生产环境里,对配置文件中的值进行了加密(比如数据库密码)。

这个加解密用的是:

xml 复制代码
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.4</version>
</dependency>

本来正常情况下,propertySource的类型大概是这样:

然后,jasypt这个加解密组件,就把上述的propertySource类型全给转成了它自己的:

java 复制代码
EncryptableMapPropertySourceWrapper

然后内部再包含了原始的propertySource:

debug发现,EncryptableMapPropertySourceWrapper中包含的原始的propertySource类型为:

复制代码
org.springframework.boot.env.OriginTrackedMapPropertySource

OriginTrackedMapPropertySource初始化

我们接下来看看,正常情况下,OriginTrackedMapPropertySource是啥时候初始化的。

打个断点:

最后发现new的时机大概是,applicationContext这个上下文完成了environmentPrepared后,就会发布一个org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent,这时候各个listener就会处理这个事件。

其中一个listener为:

java 复制代码
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

这个listener中会找到各个EnvironmentPostProcessor,这个后置处理器接口,类似于bean的那些后置处理器,也是spring给我们提供的扩展点,这个org.springframework.boot.env.EnvironmentPostProcessor主要就是方便我们对Environment进行后置处理。

在我这边,找到的Environment后置处理器还是很多:

其中的第7个ConfigDataEnvironmentPostProcessor就是本文主角:

它就会到处去找配置:

如果想要完整了解这块的流程,可以打开logger配置:

xml 复制代码
<logger name="org.springframework.boot.context.config" level="TRACE"/>
<logger name="org.springframework.boot.logging" level="TRACE"/>

大概流程如下:

查找的位置:

shell 复制代码
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataEnvironment Adding initial config data import from location 'optional:file:./;optional:file:./config/;optional:file:./config/*/' [DeferredLog.java:249]
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataEnvironment Adding initial config data import from location 'optional:classpath:/;optional:classpath:/config/' [DeferredLog.java:249]

查找的文件:

shell 复制代码
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./application.yaml [DeferredLog.java:249]
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./application.yml [DeferredLog.java:249]
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./application.properties [DeferredLog.java:249]
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./application.json [DeferredLog.java:249]
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./application.xml [DeferredLog.java:249]
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/application.yaml [DeferredLog.java:249]
04-26 15:30:47.949 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/application.yml [DeferredLog.java:249]
04-26 15:30:47.954 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/application.properties [DeferredLog.java:249]
04-26 15:30:47.954 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/application.json [DeferredLog.java:249]
04-26 15:30:47.954 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/application.xml [DeferredLog.java:249]

在计算了profile后,如:profile为dev,则:

问题原因

后来经过反复对比正常和异常情况下的代码路径,发现异常情况下,计算出来的active的profile竟然是空的。正常情况下,profile是dev。

然后重点查下面的代码:

Environment获取profile为啥是null

我们知道,在environment中包含了propertySource的list,我在对比异常情况下的list发现,如下的propertySource有差别,正常情况下,我这边,profile是在bootstrap.yml这种指定的,那就应该正常如下:

上图中,names这个数组里,包含了两个bootstrap.yml,其实就是如下这两段配置:

但是在异常情况下,names数组是空的:

切到这个propertySource的代码一看,找了下操作names数组的地方:

发现会判断propertySource是不是OriginTrackedMapPropertySource类型,而我们知道的是,那个加密组件,会把正常的OriginTrackedMapPropertySource类型的propertySource转成EncryptableMapPropertySourceWrapper,而这个EncryptableMapPropertySourceWrapper就不是OriginTrackedMapPropertySource的子类,所以这里就没法加入到names数组。

修改方式

我以前就被这个加密组件坑过一次了,改的方式就是:

直接把这个类的源码拷贝了一份,改了一下,放到项目的src下(包名不能变):

add方法的调用时机

上述这个方法什么时候调用的呢,其实就是在上文说的,applicationContext这个上下文完成了environmentPrepared后,就会发布一个org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent,这时候各个listener就会处理这个事件。

前面那个listener是下图第3个,这里我们add方法这块,就是在第一个listener中调用的:

shell 复制代码
org.springframework.cloud.bootstrap.BootstrapApplicationListener

这个listener就负责了spring cloud中boostrap阶段的工作(支持bootstrap.yml,从nacos这类配置中心读取配置)。

在这个listener中:

下图所示,这个listener中也会生成一个bootstrap阶段的applicationContext,且也会查找自己的各个propertySource作为自己的env(包括去查找各个bootstrap.yml等配置文件)

shell 复制代码
04-26 15:55:39.725 [main] TRACE [] o.s.b.c.config.ConfigDataEnvironmentContributors Processing imports [optional:file:./;optional:file:./config/;optional:file:./config/*/] [DeferredLog.java:249]
04-26 15:55:39.725 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./bootstrap.yaml [DeferredLog.java:249]
04-26 15:55:39.725 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./bootstrap.yml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./bootstrap.properties [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./bootstrap.json [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./bootstrap.xml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/bootstrap.yaml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/bootstrap.yml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/bootstrap.properties [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/bootstrap.json [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource file:./config/bootstrap.xml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataImporter Considering resource file [.] from location optional:file:./;optional:file:./config/;optional:file:./config/*/ [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLoaders Loading file [.] using loader org.springframework.boot.context.config.StandardConfigDataLoader [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataImporter Loaded resource file [.] from location optional:file:./;optional:file:./config/;optional:file:./config/*/ [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.b.c.config.ConfigDataEnvironmentContributors Imported 1 resource [file [.]] [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.b.c.config.ConfigDataEnvironmentContributors Processing imports [optional:classpath:/;optional:classpath:/config/] [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/bootstrap.yaml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/bootstrap.properties [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/bootstrap.json [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/bootstrap.xml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/config/bootstrap.yaml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/config/bootstrap.yml [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/config/bootstrap.properties [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/config/bootstrap.json [DeferredLog.java:249]
04-26 15:55:39.726 [main] TRACE [] o.s.boot.context.config.ConfigDataLocationResolver Skipping missing resource classpath:/config/bootstrap.xml [DeferredLog.java:249]

最终,bootstrap阶段这些配置,是需要合并到原来的env里的。

下图就是触发add方法的地方(就是需要修改代码的那里):