zoukankan      html  css  js  c++  java
  • SpringBoot自定义注解@YamlPropertySource加载yml或者yaml文件(扩展了@PropertySource)

    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. *&#42;/*.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信息。

  • 相关阅读:
    多模块javaweb项目构建tomcat部署
    html页面基于ajax按钮60秒倒计时
    java面向对象六原则一法则
    sessionStorage 和 localStorage 、cookie
    Is-A,Has-A,Use-A(转载)
    多例模式
    java变量类型
    单例模式
    How to modify a compiled Android application (.apk file)
    Unity3D 绘制扇形/环形
  • 原文地址:https://www.cnblogs.com/liuenyuan1996/p/11033080.html
Copyright © 2011-2022 走看看