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
    * &mdash; 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自定义注解@YamlPropertySource加载yml或者yaml文件(扩展了@PropertySource)的更多相关文章

  1. springboot属性类自动加载配置文件中的值

    springboot属性类自动加载配置文件中的值,如Person类加载在yml中配置的name,age等属性值,可以通过如下步骤获取: 类上添加@ConfigurationProperties注解,p ...

  2. SpringBoot 自定义注解 实现多数据源

    SpringBoot自定义注解实现多数据源 前置学习 需要了解 注解.Aop.SpringBoot整合Mybatis的使用. 数据准备 基础项目代码:https://gitee.com/J_look/ ...

  3. JVM自定义类加载器加载指定classPath下的所有class及jar

    一.JVM中的类加载器类型 从Java虚拟机的角度讲,只有两种不同的类加载器:启动类加载器和其他类加载器. 1.启动类加载器(Boostrap ClassLoader):这个是由c++实现的,主要负责 ...

  4. SpringBoot系列之配置文件加载位置

    SpringBoot系列之配置文件加载位置 SpringBoot启动会自动扫描如下位置的application.properties或者application.yml文件作为Springboot的默认 ...

  5. 如果你还不知道如何控制springboot中bean的加载顺序,那你一定要看此篇

    1.为什么需要控制加载顺序 springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题.在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功 ...

  6. google官方的下拉刷新+自定义上拉加载更多

    转载请标注转载:http://blog.csdn.net/oqihaogongyuan/article/details/50949118 google官方的下拉刷新+自定义上拉加载更多 现在很多app ...

  7. 自定义ProgressBar的加载效果

    三种方式实现自定义圆形页面加载中效果的进度条 To get a ProgressBar in the default theme that is to be used on white/light b ...

  8. SpringBoot自带热加载开发工具

    SpringBoot自带热加载开发工具 maven配置: <!-- SpringBoot自带热加载开发工具 --> <dependency> <groupId>or ...

  9. Spring Boot自定义配置与加载

    Spring Boot自定义配置与加载 application.properties主要用来配置数据库连接.日志相关配置等.除了这些配置内容之外,还可以自定义一些配置项,如: my.config.ms ...

随机推荐

  1. 【u114】旅行计划(12月你好)

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] 小明要去一个国家旅游.这个国家有N个城市,编号为1-N,并且有M条道路连接着,小明准备从其中一个城市出 ...

  2. [Cordova+Sencha Touch] 移动开发1 sencha 2.4.0 + 在 安卓2.3.6上使用报错 - has no method 'bind'

    Sencha Touch 2.3.2和2.4.0在安卓2.3上面用会报错,具体报错信息如下: 解决办法是: 打开文件:你的file:///android_asset/www/sencha-touch- ...

  3. Troubleshooting routing topology based on a reference topology

    In one embodiment, a computing device (e.g., border router or network management server) transmits a ...

  4. Spring MVC【入门】一篇!

    MVC 设计概述 在早期 Java Web 的开发中,统一把显示层.控制层.数据层的操作全部交给 JSP 或者 JavaBean 来进行处理,我们称之为 Model1:     出现的弊端: JSP ...

  5. Qt 格式转换问题 记录(好多方法)

    用Qt经常头痛于一些格式不能通用的问题 在此记录备用 1 (20120112)QString转为Char * QString *str; char *a; str="hello word ! ...

  6. NAT对于网络上的其它人都不可见(但可以转发端口),Bridged和网络里的其它物理机没区别,Internal只能在虚拟机之间可见,Host-only只对主机可见(最符合我的服务器需求),最多可设置8块网卡实现混杂

    VirtualBox 可以为每一个虚拟机分配8个网卡.每一个网卡的连接方式可以选为下列之一: Not attached Network Address Translation (NAT) Bridge ...

  7. Blend_技巧篇_导入PSD文件制作ToggleButton (Z)

    原文:Blend_技巧篇_导入PSD文件制作ToggleButton (Z) 系统: Win7sp1 32位 IDE: Microsoft VisualStudio 2013 Ultimate Ble ...

  8. Coverage数据拓扑

    什么是Coverage?   Coverage数据模型源于ESRI公司1981年推出的第一个商业GIS软件——ArcInfo.也被称为地理相关数据模型(Georelational Data Model ...

  9. windows消息值全部定义,从消息值得到消息名称(系统消息定义从0到1023,从1024开始就是WM_USER,但是中间有325个WM_undefined消息,估计是备用,另外各控件都有一些reserved消息,也是为了备用)LostSpeed

    前言 在逆向算法扫描插件时, 遇到一个windows消息值在msdn中没有定义. 去查资料, 有个老外将全部windows消息值和消息名称定义都码好了:) 写个测试程序, 封装了一个接口, 从消息值得 ...

  10. doker基础

    去 Docker Hub 上拉取一个叫 hello-world 的集装箱docker pull hello-world然后让这个集装箱跑起来docker run hello-world查看本机所安装的 ...