springboot源码分析14-ApplicationContextInitializer原理Springboot中PropertySource注解多环境支持以及原理

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 摘要:Springboot中PropertySource注解的使用一文中,详细讲解了PropertySource注解的使用,通过PropertySource注解去加载指定的资源文件、然后将加载的属性注入到指定的配置类,@value以及@ConfigurationProperties的使用。

摘要: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 {

String name() default "";

String[] value();

boolean ignoreResourceNotFound() default false;

String encoding() default "";

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

7 }

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

1 public interface PropertySourceFactory {

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

3 }

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

1 public class DefaultPropertySourceFactory implements PropertySourceFactory {

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

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

}

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 {

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

if (!StringUtils.hasLength(name)) {

name = null;

}

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

if (!StringUtils.hasLength(encoding)) {

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. 开始解析nameencoding值。

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 {

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

this.resourceName = null;

4  }

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

1 private static String getNameForResource(Resource resource) {

String name = resource.getDescription();

if (!StringUtils.hasText(name)) {

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

}

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}表达式可以动态的进行配置,从而达到动态切换不同的配置文件了。


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Java相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。
 


相关文章
|
2月前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
144 1
|
2月前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
73 0
|
2天前
|
Java Spring 容器
springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
15 4
|
5天前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
1月前
|
Java 应用服务中间件 API
Vertx高并发理论原理以及对比SpringBoot
Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
Vertx高并发理论原理以及对比SpringBoot
|
11天前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
11天前
|
Java API Spring
springBoot:注解&封装类&异常类&登录实现类 (八)
本文介绍了Spring Boot项目中的一些关键代码片段,包括使用`@PathVariable`绑定路径参数、创建封装类Result和异常处理类GlobalException、定义常量接口Constants、自定义异常ServiceException以及实现用户登录功能。通过这些代码,展示了如何构建RESTful API,处理请求参数,统一返回结果格式,以及全局异常处理等核心功能。
|
17天前
|
Java Spring 容器
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
这篇文章讨论了在Spring Boot 3.2.1版本中,同名同类型的bean和@Service注解类之间冲突的问题得到了解决,之前版本中同名bean会相互覆盖,但不会在启动时报错,而在配置文件中设置`spring.main.allow-bean-definition-overriding=true`可以解决这个问题。
51 0
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
|
22天前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
42 2
|
1月前
|
Java 开发者 数据格式
【Java笔记+踩坑】SpringBoot基础4——原理篇
bean的8种加载方式,自动配置原理、自定义starter开发、SpringBoot程序启动流程解析
【Java笔记+踩坑】SpringBoot基础4——原理篇