1:概述
SpringBoot的@PropertySource注解只支持加载 properties结尾的文件。当使用@ConfigurationProperties
注解配合@EnableConfigurationProperties注解将配置转换为JavaBean时,可能需要配合@PropertySource
注解加载指定的配置文件。所以为了支持以yml或者yaml文件,我自定义了注解@YmlPropertySource。
2:实现
声明注解@YmlPropertySource
/**
* 类描述: load yml or yaml file into {@link org.springframework.core.env.Environment}
*
* @author liuenyuan
* @date 2019/6/16 20:12
* @describe
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YmlPropertySource {
/**
* Indicate the name of this property source. If omitted, a name will
* be generated based on the description of the underlying resource.
*
* @see org.springframework.core.env.PropertySource#getName()
* @see org.springframework.core.io.Resource#getDescription()
*/
String name() default "";
/**
* Indicate the resource location(s) of the properties file to be loaded.
* Both traditional and XML-based properties file formats are supported
* — for example, {@code "classpath:/com/myco/app.yml|yaml"}
*
Resource location wildcards (e.g. **/*.yml|yaml) are not permitted;
* each location must evaluate to exactly one {@code .properties} resource.
*
${...} placeholders will be resolved against any/all property sources already
* registered with the {@code Environment}. See {@linkplain YmlPropertySource above}
* for examples.
*
Each location will be added to the enclosing {@code Environment} as its own
* property source, and in the order declared.
*/
String[] value();
}
具体实现如下
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.*;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import java.io.IOException;
import java.util.*;
/**
* 类描述: {@link YmlPropertySource} bean post processor.this class convert the yml or yaml file
* {@link YmlPropertySource#value()} to {@link PropertiesPropertySource},and add the property source
* named {@link YmlPropertySource#name()} into {@link Environment}.When you use this annotation,you
* must for follow example:
* {@code
* @link @ConfigurationProperties(prefix = "person")
* @link @YmlPropertySource(value = {"classpath:/hello.yml"}, name = "hello")
* @link @Data
* public class PersonProperties {
*
* private String name;
*
* private Integer age;
*
* private String school;
* }}
*
* @author liuenyuan
* @date 2019/6/16 20:13
* @describe
* @see YmlPropertySource
* @see InstantiationAwareBeanPostProcessorAdapter
* @see EnvironmentAware
* @see ResourceLoaderAware
*/
@Slf4j
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class YmlPropertySourceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements EnvironmentAware, ResourceLoaderAware {
private Environment environment;
private ResourceLoader resourceLoader;
@Override
public void setEnvironment(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "environment must be instance of ConfigurableEnvironment.");
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
YmlPropertySource ymlPropertySource = AnnotationUtils.findAnnotation(bean.getClass(), YmlPropertySource.class);
Map> ymlPropertySourceMap = new LinkedHashMap<>();
if (ymlPropertySource != null) {
String[] value = ymlPropertySource.value();
String name = ymlPropertySource.name();
List resources = new ArrayList<>();
Arrays.stream(value).forEach(location -> {
Resource resource = resourceLoader.getResource(location);
try {
if (resource.getInputStream() != null) {
resources.add(resource);
}
} catch (IOException e) {
log.warn("file {} not found.", location);
}
});
ymlPropertySourceMap.put(name, resources);
}
if (!ymlPropertySourceMap.isEmpty()) {
ymlPropertySourceMap.forEach((name, resources) -> {
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(resources.toArray(new Resource[resources.size()]));
PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource(name, yamlPropertiesFactoryBean.getObject());
ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
MutablePropertySources propertySources = configurableEnvironment.getPropertySources();
propertySources.addLast(propertiesPropertySource);
});
}
return true;
}
}
想法
使用InstantiationAwareBeanPostProcessorAdapter的postProcessAfterInstantiation(Object bean, String beanName)方法,然后通过YamlPropertiesFactoryBean将yml|yaml文件转换为properties文件,然后通过
实现EnvironmentAware接口,将配置文件属性写入到spring的Environment环境中。但是该实现有点
缺陷,就是如果使用@ConfigurationProperties和@EnableConfigurationProperties将配置属性
转换为JavaBean时,需要将@YmlProperySource注解标