SpringBoot自定义注解@YamlPropertySource加载yml或者yaml文件(扩展了@PropertySource)
2019-06-16 21:40
441 查看
1:概述
SpringBoot的
@PropertySource注解只支持加载 properties结尾的文件。当使用@ConfigurationProperties
注解配合
@EnableConfigurationProperties注解将配置转换为JavaBean时,可能需要配合@PropertySource
注解加载指定的配置文件。所以为了支持以
yml或者yaml文件,我自定义了注解
@YamlPropertySource。
2:实现
声明注解@YamlPropertySource
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.lang.annotation.*;
/**
* yaml property source and extension {@link PropertySource}
*
* @author liuenyuan
* @see org.springframework.context.annotation.PropertySource
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(YamlPropertySources.class)
public @interface YamlPropertySource {
/**
* 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.
* <p>Both traditional and XML-based properties file formats are supported
* — for example, {@code "classpath:/com/myco/app.properties"}
* or {@code "file:/path/to/file.xml"}.
* <p>Resource location wildcards (e.g. **/*.properties) are not permitted;
* each location must evaluate to exactly one {@code .properties} resource.
* <p>${...} placeholders will be resolved against any/all property sources already
* registered with the {@code Environment}. See {@linkplain PropertySource above}
* for examples.
* <p>Each location will be added to the enclosing {@code Environment} as its own
* property source, and in the order declared.
*/
String[] value();
/**
* Indicate if failure to find the a {@link #value() property resource} should be
* ignored.
* <p>{@code true} is appropriate if the properties file is completely optional.
* Default is {@code false}.
*
* @since 4.0
*/
boolean ignoreResourceNotFound() default false;
/**
* A specific character encoding for the given resources, e.g. "UTF-8".
*
* @since 4.3
*/
String encoding() default "";
/**
* Specify a custom {@link PropertySourceFactory}, if any.
* <p>By default, a default factory for standard resource files will be used.
*
* @see org.springframework.core.io.support.DefaultPropertySourceFactory
* @see org.springframework.core.io.support.ResourcePropertySource
* @since 4.3
*/
Class<? extends PropertySourceFactory> factory() default YamlPropertySourceFactory.class;
}
/**
* @author liuenyuan
* @see YamlPropertySource
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YamlPropertySources {
YamlPropertySource[] 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 YamlPropertySource} bean post processor.this class convert the yml or yaml file {@link YamlPropertySource#value()} to {@link PropertiesPropertySource},and add the property source
* named {@link YamlPropertySource#name()} into {@link Environment}.When you use this annotation,you
* must for follow example:
* <pre>{@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;
* }}</pre>
*
* @author liuenyuan
* @date 2019/6/16 20:13
* @describe
* @see YamlPropertySource
* @see InstantiationAwareBeanPostProcessorAdapter
* @see EnvironmentAware
* @see ResourceLoaderAware
*/
@Slf4j
@Configuration(value = YamlPropertySourceAnnotationPostProcessor.BEAN_NAME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class YamlPropertySourceAnnotationPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements EnvironmentAware, ResourceLoaderAware {
public final static String BEAN_NAME = "yamlPropertySourceAnnotationPostProcessor";
private Environment environment;
private ResourceLoader resourceLoader;
private final List<String> propertySourceNames = new ArrayList<>();
private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new YamlPropertySourceFactory();
@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 {
// Process any @PropertySource annotations
Set<YamlPropertySource> yamlPropertySources = AnnotationUtils.getRepeatableAnnotations(bean.getClass(),
YamlPropertySource.class, YamlPropertySources.class);
if (!yamlPropertySources.isEmpty()) {
Set<AnnotationAttributes> attributesSet = new LinkedHashSet<>(yamlPropertySources.size());
for (YamlPropertySource yamlPropertySource : yamlPropertySources) {
AnnotationAttributes attributes = AnnotationUtils.getAnnotationAttributes(bean.getClass(),
yamlPropertySource);
attributesSet.add(attributes);
}
for (AnnotationAttributes propertySource : attributesSet) {
if (this.environment instanceof ConfigurableEnvironment) {
try {
processPropertySource(propertySource);
} catch (IOException e) {
log.warn("exception message: {}", e.getMessage());
}
} else {
log.warn("Ignoring @YamlPropertySource annotation on [" + bean.getClass() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
}
return true;
}
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @YamlPropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
} catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (log.isInfoEnabled()) {
log.info("Properties or Yml or Yaml location [" + location + "] not resolvable: " + ex.getMessage());
}
} else {
throw ex;
}
}
}
}
private void addPropertySource(PropertySource<?> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
if (this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
PropertySource<?> existing = propertySources.get(name);
if (existing != null) {
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
} else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
return;
}
}
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
} else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}
}
想法
使用
InstantiationAwareBeanPostProcessorAdapter的postProcessAfterInstantiation(Object bean, String beanName)方法,然后通过
YamlPropertiesFactoryBean将yml|yaml文件转换为properties文件,然后通过
实现
EnvironmentAware接口,将配置文件属性写入到spring的
Environment环境中。但是该实现有点
缺陷,就是如果使用
@ConfigurationProperties和
@EnableConfigurationProperties将配置属性
转换为
JavaBean时,需要将
@YamlProperySource注解标注到该
JavaBean上。因为我无法在Bean实例化之前获取Bean所有的Bean信息。
相关文章推荐
- springboot 加载自定义yml文件
- Spring boot bootstrap.yml文件使用遇见的坑Failed to load property source from location 'classpath:/bootstrap.
- Springboot通过PropertySource注解加载指定的配置文件,出现以下问题
- SpringBoot如何导入自定义配置的yml文件
- @PropertySource 注解实现读取 yml 文件
- springboot 自定义yml 配置文件
- Eclipse安装了SpringSource Tool Suite插件后,编辑SpringBoot项目的yml配置文件仍然没有提示信息的解决办法
- spring框架的XML扩展特性:让spring加载和解析你自定义的XML文件
- Spring Boot 自定义PropertySourceLoader
- SpringBoot如何导入自定义配置的yml文件
- spring框架的XML扩展特性:让spring加载和解析你自定义的XML文件
- 详解Spring Boot加载properties和yml配置文件
- SpringBoot如何导入自定义配置的yml文件
- Spring Boot自定义加载yml实现,附源码解读
- spring boot加载多个配置文件,yml或properties类型
- 第五章:SpringBoot配置(@PropertySource @ImportResource @Bean 注解)
- spring框架的XML扩展特性:让spring加载和解析你自定义的XML文件
- spring-boot读取自定义文件三:对于yml文件读取的解析
- SpringBoot如何导入自定义配置的yml文件
- spring框架的XML扩展特性:让spring加载和解析你自定义的XML文件