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 {
springboot 原理解析
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 = String("name");
4  if (!StringUtils.hasLength(name)) {
5  name = null;
6  }
7  String encoding = String("encoding");
8  if (!StringUtils.hasLength(encoding)) {
9  encoding = null;
10  }
11  String[] locations = StringArray("value");
12  boolean ignoreResourceNotFound = Boolean("ignoreResourceNotFound");
13  Class factoryClass = Class("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 = solveRequiredPlaceholders(location);
19  Resource resource = Resource(resolvedLocation);
20  atePropertySource(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(Resource()),PropertiesLoaderUtils.loadProperties(resource));
3  sourceName = null;
4  }
上述代码,我们重点关注⼀下name值的⽣成逻辑。也就是getNameForResource中的处理逻辑,如下所⽰:
1 private static String getNameForResource(Resource resource) {
2  String name = Description();
3  if (!StringUtils.hasText(name)) {
4  name = Class().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"。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。