PropertySource 注解读取 Yaml 配置文件

  1. 1. 1. @PropertySource 注解不支持 Yaml 格式的配置文件
    1. 1.1. 1.1 为什么不支持
    2. 1.2. 1.2 扩展支持
  2. 2. 2. 配置的文件不支持 profile active 配置

Spring 中常用 @PropertySource 注解来加载指定配置文件,但是 @PropertySource 注解也有一些限制,如:

  1. 无法加载 Yaml 格式的配置
  2. 配置的文件不支持 profile active 配置

本文将通过扩展的方式解决以上的需求。

1. @PropertySource 注解不支持 Yaml 格式的配置文件

1.1 为什么不支持

我们先看一下 @PropertySource 注解的声明(以下删去了其它无关代码,只截取了关键部分)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public @interface PropertySource {
// ...
String[] value();

// ...

/**
* Specify a custom {@link PropertySourceFactory}, if any.
* <p>By default, a default factory for standard resource files will be used.
* @since 4.3
* @see org.springframework.core.io.support.DefaultPropertySourceFactory
* @see org.springframework.core.io.support.ResourcePropertySource
*/
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

@PropertySource 注解的 factory 属性用于配置 PropertySource 对象的生成方式。在末配置的情况下,默认使用 PropertySourceFactory

再来看 ConfigurationClassParser 类中的 processPropertySource 方法,该方法用于处理 Configuration 类上的 @PropertySource 注解。 当注解上的 factory 属性为 PropertySourceFactory.class 时使用默认的 DefaultPropertySourceFactory 工厂类来生成 PropertySource 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// org.springframework.context.annotation.ConfigurationClassParser.java

private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();

private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");

// ...

Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

// ...
}

DefaultPropertySourceFactory 类很简单,默认的工厂方法直接将传过来的 Resource 交给 ResourcePropertySource 处理,而 ResourcePropertySource 类 只能处理 .properties.xml 类型的文件。

1
2
3
4
5
6
7
public class DefaultPropertySourceFactory implements PropertySourceFactory {

@Override
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
}

1.2 扩展支持

经过以上分析就可以看出,只用简单的实现 PropertySourceFactory 或继承 DefaultPropertySourceFactory,返回支持 Yaml 配置文件格式的 PropertySource 类既可。

Spring Boot 自带了一些 PropertySourceLoader 用于加载配置文件并产生 PropertySource 对象。所以我们的 Factory 类只用将生成 PropertySource 对象 的操作委托给 YamlPropertySourceLoader 类既可,完整实现如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null){
return super.createPropertySource(name, resource);
}
CompositePropertySource propertySource = new CompositePropertySource(ResourceUtils.getNameForResource(resource.getResource()));

new YamlPropertySourceLoader()
.load(resource.getResource().getFilename(), resource.getResource())
.forEach(propertySource::addPropertySource);

return propertySource;
}
}

2. 配置的文件不支持 profile active 配置

正常我们生产环境会使用另一套和开发环境不一致的配置,一般会通过设计 spring.profile.active 环境变量来启用,这样我们可以根据不同环境启用不同 配置。

如生产设置 spring.profile.active=prdapplication.yamlapplication-prd.yaml 都生效。但该环境变量是不在使用 @PropertySource 加载的配置文件上生效的。

1
2
@PropertySource(value = "classpath:/cfg/sdk.yaml", factory = YamlPropertyLoaderFactory.class)
public class ExampleConfiguration {}

怎样才能使 sdk.yamlsdk-prd.yaml 同时加载呢?这里有个不是很完美的方法可以实现。

1
2
3
4
5
6
7
@PropertySources({
@PropertySource(value = "classpath:/cfg/sdk.yaml", factory = YamlPropertyLoaderFactory.class),
@PropertySource(value = "classpath:/cfg/sdk-${spring.profiles.active}.yaml",
factory = YamlPropertyLoaderFactory.class,
ignoreResourceNotFound = true)
})
public class ExampleConfiguration {}

通过使用 @PropertySources 注解,同时配置两个 @PropertySource,并让第二个 @PropertySource 配置可以在资源找不到时不报错。同时在 @PropertySourcevalue 中可以使用属性占位符,动态加载 spring.profiles.active 变量指定的配置。

通过这样的方式,基本实现了需求,但还是有些问题:

  1. 每个配置文件都需要配置多个
  2. spring.profiles.active 配置了多个时不可用

所以这种解决方案有得有失,有更好的方法欢迎告诉我。