• Springboot中PropertySource注解多环境支持以及原理


    摘要:Springboot中PropertySource注解的使用一文中,详细讲解了PropertySource注解的使用,通过PropertySource注解去加载指定的资源文件、然后将加载的属性注入到指定的配置类,@value以及@ConfigurationProperties的使用。但是也遗留一个问题,PropertySource注解貌似是不支持多种环境的动态切换?这个问题该如何解决呢?我们需要从源码中看看他到底是否支持。

    首先,我们开始回顾一下上节课说的PropertySource注解的使用,实例代码如下:

    1 @PropertySource( name="jdbc-bainuo-dev.properties",

    2 value={"classpath:config/jdbc-bainuo-dev.properties"},ignoreResourceNotFound=false,encoding="UTF-8")

    我们使用了PropertySource注解中的参数有:name、value、ignoreResourceNotFound、encoding。其实PropertySource注解还有一个参数,那就是factory,该参数默认的值为PropertySourceFactory.class。

    PropertySource注解的定义如下:

    1 public @interface PropertySource {

    2  String name() default "";

    3  String[] value();

    4  boolean ignoreResourceNotFound() default false;

    5  String encoding() default "";

    6  Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

    7 }

    我们不妨先看一下PropertySourceFactory接口是做什么的?该接口的核心定义代码如下:

    1 public interface PropertySourceFactory {

    2  PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;

    3 }

    上述代码中,PropertySourceFactory接口仅仅提供了一个createPropertySource方法,该方法就是创建PropertySource实例对象的,关于PropertySource的架构可以参考前面的系列文章进行学习,既然是接口,那肯定有实现类吧?该接口的默认实现类为DefaultPropertySourceFactory,代码如下:

    1 public class DefaultPropertySourceFactory implements PropertySourceFactory {

    2  public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {

    3  return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));

    4  }

    5 }

    首先判断name参数值是否为空,如果不为空直接实例化ResourcePropertySource并将name参数值进行传递,否则直接实例化ResourcePropertySource。看到这个地方的处理,感觉也没什么神奇的地方,那么问题来了,我们思考如下三个问题:DefaultPropertySourceFactory 类什么时候被Spring框架调用呢?Name参数值是如何传递过来的呢?ResourcePropertySource实例化的时候做了什么呢?我们一个个的来看源码进行分析。

    1.1. DefaultPropertySourceFactory 类什么时候被Spring框架调用呢
    第一个问题:DefaultPropertySourceFactory 类什么时候被Spring框架调用呢?这个我们就需要看一下我们定义的PropertySource注解是如何被Spring框架解析的?经过我的一系列排查,我找到了。Spring框架开始解析PropertySource注解的方法位于ConfigurationClassParser类中,为了防止大量的跟踪源码跟踪丢失了,自己也糊涂了。我们直奔主题,看一下processPropertySource方法,如下所示:

    1 private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();

    2  private void processPropertySource(AnnotationAttributes propertySource) throws IOException {

    3  String name = propertySource.getString("name");

    4  if (!StringUtils.hasLength(name)) {

    5  name = null;

    6  }

    7  String encoding = propertySource.getString("encoding");

    8  if (!StringUtils.hasLength(encoding)) {

    9  encoding = null;

    10  }

    11  String[] locations = propertySource.getStringArray("value");

    12   boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

    13  Class factoryClass = propertySource.getClass("factory");

    14  PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?

    15  DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

    16  for (String location : locations) {

    17  try {

    18  String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);

    19  Resource resource = this.resourceLoader.getResource(resolvedLocation);

    20  addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));

    21  }

    22  catch (IllegalArgumentException ex) {

    23  if (ignoreResourceNotFound) {

    24  if (logger.isInfoEnabled()) {

    25  logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());

    26  }

    27  }

    28      else {

    29  throw ex;

    30      }

    31  }

    32  catch (IOException ex) {

    33  }

    34   }

    上述代码的逻辑分为如下几个核心的步骤:

    1. 开始解析name、encoding值。

    2. 解析value(数组)以及ignoreResourceNotFound值。

    3. 解析factory,如果该值没有配置,默认为PropertySourceFactory则直接实例化DefaultPropertySourceFactory类,否则开始实例化自定义的类。换言之factory的处理类我们是可以进行自定义的。BeanUtils.instantiateClass是Spring中比较常用的一个工具类,其内部就是通过反射手段实例化类,在这里我们就不一一讲解了。

    4. 循环遍历所有的location值,进行如下的处理。

         4.1.对location进行SPEL表达式的解析。比如当前的配置环境中有一个属性为app=shareniu,我们配置的location为${app}最终值为shareniu。通过这里的处理逻辑可以知道location支持多环境的切换以及表达式的配置。

         4.2.使用资源加载器resourceLoader将resolvedLocation抽象为Resource。

         4.3.调用addPropertySource属性进行处理。将指定的资源处理之后,添加到当前springboot运行的环境中,这个前面的章节也详细讲解过类似的,在这里就不详细说明了。注意:这里调用了DefaultPropertySourceFactory类中的createPropertySource方法了。

    5.如果上述的任意步骤报错,则开始查找ignoreResourceNotFound的值,如果该值为treu,则忽略异常,否则直接报错。在这里我们可以看出ignoreResourceNotFound参数值的配置非常的重要。

    1.2. ResourcePropertySource
    我们看一下ResourcePropertySource类的构造函数,主要看一下没有name参数的构造函数,如下所示:

    1 public ResourcePropertySource(EncodedResource resource) throws IOException {

    2  super(getNameForResource(resource.getResource()),PropertiesLoaderUtils.loadProperties(resource));

    3  this.resourceName = null;

    4  }

    上述代码,我们重点关注一下name值的生成逻辑。也就是getNameForResource中的处理逻辑,如下所示:

    1 private static String getNameForResource(Resource resource) {

    2  String name = resource.getDescription();

    3  if (!StringUtils.hasText(name)) {

    4  name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);

    5  }

    6  return name;

    7  }

    通过resource中的getDescription方法获取name值。如果name值为空,则重新生成,否则直接返回。大家又兴起可以看看resource中各个子类的定义以及使用。

    PropertiesLoaderUtils.loadProperties(resource)毋庸置疑就是加载指定的属性文件了。

    1.3. PropertySource多环境配置以及表达式使用
    在springboot中,可以通过设置spring.profiles.active属性,达到不同环境配置文件的动态切换。我们看一下这种方式如何使用,首先在application.properties增加如下的信息:

    spring.profiles.active=dev

    application.properties文件位置如下图所示:

    然后,我们修改CustomerDataSourceConfig1类,这个类的配置以及启动类进行测试可以参考上一篇文章进行学习。

    1 @PropertySource( name="jdbc-bainuo-dev.properties",value= {"classpath:config/jdbc-bainuo-$ {spring.profiles.active}.properties"},ignoreResourceNotFound=false,encoding="UTF-8")

    2 public class CustomerDataSourceConfig1  {

    3 }

    这里注意value的值已经修改为了:"classpath:config/jdbc-bainuo-${spring.profiles.active}.properties"。

    ${spring.profiles.active}表达式可以动态的进行配置,从而达到动态切换不同的配置文件了。
    ————————————————
    版权声明:本文为CSDN博主「分享牛」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_30739519/article/details/78800490

  • 相关阅读:
    [SUCTF 2019]Game
    [RoarCTF2019]黄金6年
    espcms代码审计第一弹
    初识搜索
    安恒2018年三月月赛MISC蜘蛛侠呀
    [De1CTF 2019]SSRF Me
    网鼎杯 fakebook
    [0CTF 2016]piapiapia
    [RoarCTF 2019]Easy Java
    [CISCN2019 华北赛区 Day1 Web1]Dropbox
  • 原文地址:https://www.cnblogs.com/suizhikuo/p/12909007.html
Copyright © 2020-2023  润新知