原来配置文件走nacos都是用的nacos config的方式配置的,比如如下的配置方式:
yaml
spring:
application:
name: test
cloud:
nacos:
config:
enabled: true
server-addr: ${nacos.server-addr}
refresh-enabled: true
namespace: ${nacos.namespace}
extension-configs:
- data-id: ${spring.application.name}.yml
group: ${spring.cloud.nacos.config.group}
refresh: true
- data-id: spring-datasource.yml
group: ${spring.cloud.nacos.config.group}
refresh: true
- data-id: spring-redis.yml
group: ${spring.cloud.nacos.config.group}
refresh: true
file-extension: yaml
group: DEFAULT_GROUP
这种方式,如果nacos配置的密码是加密的,只需写一个类(CustomNacosPropertySourceLocator)继承NacosPropertySourceLocator,启动的时候覆盖掉原来的NacosPropertySourceLocator,就能解密密码。 但是最近新项目采用了springboot3来实现的,加载文件换了一种方式,走的如下的配置方式:
yaml
spring:
application:
name: test
config:
import:
- classpath:spring-nacos.yml
- nacos:spring-datasource.yml
spring.config.import是springboot2.4以后加的加载config的方式,支持加载外部文件,以及配置中心定义的文件。 这种时候自定义的CustomNacosPropertySourceLocator就不起作用了。PropertySourceLocator是springcloud中Bootstrap中接口,Spring Cloud 2020.0.0版本之后,为了简化依赖管理,spring-cloud-starter-bootstrap
不再默认包含在其他starter中,如果需要开启bootstrap需要引入
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
这时bootstrap.yml文件是生效的。下面分析一下PropertySourceLocator接口是怎么生效的。
PropertySourceBootstrapConfiguration
负责在bootstrap阶段加载配置文件。其中有一段源码是这样的
ini
private void doInitialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection<PropertySource<?>> source = locator.locateCollection(environment);
if (source == null || source.size() == 0) {
continue;
}
List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {
if (p instanceof EnumerablePropertySource<?> enumerable) {
sourceList.add(new BootstrapPropertySource<>(enumerable));
}
else {
sourceList.add(new SimpleBootstrapPropertySource(p));
}
}
logger.info("Located property source: " + sourceList);
composite.addAll(sourceList);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
for (PropertySource<?> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
}
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment);
setLogLevels(applicationContext, environment);
handleProfiles(environment);
}
}
其中this.propertySourceLocators是spring容器中注册的所有的PropertySourceLocator实例。在bootstrap阶段会遍历所有的PropertySourceLocator来调用它的locateColletion方法。再看一下NacosPropertySourceLocator是怎么实现的。它的locateCollection用的默认实现
ini
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator, Environment environment) {
PropertySource<?> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
}
if (propertySource instanceof CompositePropertySource) {
Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource).getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList<>();
for (PropertySource<?> p : sources) {
if (p != null) {
filteredSources.add(p);
}
}
return filteredSources;
}
else {
return List.of(propertySource);
}
}
默认实现会调用locate方法,所以再看一下locate方法是怎么实现的
ini
@Override
public PropertySource<?> locate(Environment env) {
nacosConfigProperties.setEnvironment(env);
ConfigService configService = nacosConfigManager.getConfigService();
if (null == configService) {
log.warn("no instance of config service found, can't load config from nacos");
return null;
}
long timeout = nacosConfigProperties.getTimeout();
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
timeout);
String name = nacosConfigProperties.getName();
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
loadSharedConfiguration(composite);
loadExtConfiguration(composite);
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
最后调用了loadSharedConfiguration、loadExtConfiguration、loadApplicationConfiguration三个方法,其中loadExtConfiguration加载的就是在配置文件中配置的extension-configs中的配置文件,在spring.config.import中配置的文件是无法识别的,所以会失效。
改用其他的实现方案,实现EnvironmentPostProcessor接口,主要方法:
ini
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources sources = environment.getPropertySources();
String nacosSecret = getKey(environment);
for (PropertySource<?> source : sources) {
if (source instanceof EnumerablePropertySource) {
Map<String, Object> map = (Map)source.getSource();
for(Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
if (entry.getValue() != null) {
String value = entry.getValue().toString();
if (value.contains("ENC(")) {
Matcher matcher = ENC_PATTERN.matcher(value);
if (matcher.find()) {
String encryptedStr = matcher.group(1);
String decryptVal = null;
try {
decryptVal = AesUtils.decrypt(encryptedStr, nacosSecret);
} catch (Exception e) {
throw new RuntimeException(e);
}
String val = value.replace("ENC(" + encryptedStr + ")", decryptVal);
System.out.println(val);
map.put(key, val);
}
}
}
}
}
}
}
这样就可以实现配置文件解密了