文章目录
系列文章主旨
从如何把自己的Bean注册到Spring容器开始,从该点出发,每篇只关注一个核心流程的原理、源码,再由该篇带出引申出来的其他Spring知识点,继续剖析,最终达到理解Spring核心原理的目的;
上篇内容
分析了spring如何通过内置的,bean工厂后置处理器,解析配置类,识别配置类注解@Bean后,存储在config属性上,后续解析为BD,最后注册到spring容器中;
详情:【Spring】Spring原理源码解析(四)-- Spring在解析配置类时,怎么识别并处理@Bean的;
本篇内容
1、补充spring容器在初始化Environment环境组件时,是怎么识别我们jvm、系统运行环境配置的原理源码;
2、从上一篇分析的spring内部实现的ConfigurationClassPostProcessor继续入手,分析spring在解析配置类的时候,又是怎么识别@PropertySources,加入自定义配置到spring环境的原理源码;
核心原理
代码示例
配置文件
两个,分别是
test1.yml,内容为 > test: 1;
test2.yml,内容为 > test: 2;
test: 1
test:2
@PropertySource配置类
作用为:
读取(占位符)${activeEnv}.yml文件
然后把配置文件中的test设置到test属性中
@Data
@Configuration
@PropertySource(value = {"classpath:/${activeEnv}.yml"})
public class PropertySourceCls {
@Value(value = "${test}")
private String test;
}
JVM参数配置
activeEnv占位符配置为:test2
test:4
-DactiveEnv=test2 -Dtest=4
环境变量配置
test=3
test=3
启动spring容器,获取test属性,得到读取配置的默认优先级
通过变换配置打印test属性,总结默认优先级为:
JVM参数 > 环境变量 > @PropertySource加入配置
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertySourceCls.class);
PropertySourceCls propertySourceCls = context.getBean(PropertySourceCls.class);
System.out.println(propertySourceCls.getTest());
打印结果:
JVM配置: -DactiveEnv=test2,-Dtest=4
环境配置:test=3
配置文件:test1.yml,内容 test=1;test2.yml,内容test=2
打印 -> 4
JVM配置: -DactiveEnv=test2
环境配置:test=3
配置文件:test1.yml(内容test=1);test2.yml(内容test=2)
打印 -> 3
JVM配置: -DactiveEnv=test2
环境配置:
配置文件:test1.yml(内容test=1) ;test2.yml(内容test=2)
打印 -> 2
JVM配置: -DactiveEnv=test1
环境配置:
配置文件:test1.yml(内容test=1) ;test2.yml(内容test=2)
打印 -> 1
Spring识别jvm、环境配置
图解
核心处理逻辑
spring容器在进行初始化时,会初始化环境,对应的是Enviroment组件;该组件有属性propertySourceList,用来记录环境变量;
然后通过java自带的System获取得到jvm以及系统环境参数,解析后加入到propertySourceList存储;
进阶源码解析
实例化环境入口
跟踪到实例化容器,先会实例化reader,而在reader初始化时,就会顺便初始化环境Enviroment;
/**
* org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext()
*/
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
getOrCreateEnvironment()方法就是初始化环境的入口
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry,
// 创建环境的核心入口
getOrCreateEnvironment(registry));
}
继续跟进
private static Environment getOrCreateEnvironment(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry instanceof EnvironmentCapable) {
return ((EnvironmentCapable) registry).getEnvironment();
}
// 默认为基本环境
return new StandardEnvironment();
}
StandardEnvironment是AbstractEnvironment的子类,在实例化StandardEnvironment时,就会走到父类构造方法,会发现其实调用到父类AbstractEnvironment的构造方法;
在父类的构造方法中,会把存储配置集合属性propertySources存入到方法customizePropertySources(),完成实例化;
/**
* 存储配置集合的属性
*/
private final MutablePropertySources propertySources = new MutablePropertySources();
// ...忽略非相关
public AbstractEnvironment() {
// 实际又会调用到子类的customizePropertySources()方法
customizePropertySources(this.propertySources);
}
MutablePropertySources可变属性对象略览
配置集合对象MutablePropertySources,实际上就是封装了可变的集合;
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
// .. 维护属性的一些方法,addLast加入到集合最后,等等...
}
读取jvm、系统环境参数,维护到环境的属性中
这里就会发现,先读取jvm参数,然后加入到propertySources中;
接着读取系统环境变量,也加入到propertySources中;
这里很重要的可以发现,他们是有顺序加入的,因此可以从结果预测,同一个配置名,会从jvm优先获取,再到系统环境变量;
/**
* org.springframework.core.env.StandardEnvironment#customizePropertySources
*/
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
获取jvm参数
通过java的System获取到解析好的jvm参数
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemProperties() {
try {
// 通过java的System获取到jvm参数
return (Map) System.getProperties();
}
catch (AccessControlException ex) {
// 异常处理..
}
}
获取系统环境参数
通过java的System获取到解析好的系统环境参数
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemEnvironment() {
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
try {
// 通过java的System获取到系统环境参数
return (Map) System.getenv();
}
catch (AccessControlException ex) {
// 异常处理..
}
}
@PropertySources识别配置文件并维护到环境
图解
核心处理逻辑
同样是在解析配置类时,org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass,会解析@PropertySources/@PropertySource注解;
然后解析注解内容,得到通过配置的资源文件字符串,解析占位符,得到真正的位置,然后通过文件位置构建成PropertySource对象后,加入到环境的propertySources集合、sourceNames中;
后续获取属性值时,就可以通过这种方式获取到;
进阶源码解析
入口
老地方,解析配置类时,就可以看到解析@PropertySources/@PropertySource注解的方法;
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 忽略非相关..
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 忽略非相关..
}
继续跟进,处理单个注解
1、先是得到注解上的配置值,名称、解析资源文件的编码、资源文件路径;
2、然后先是解析资源文件路径,把其中的占位符替换掉;
3、然后通过解析后的文件路径,读取得到资源文件;
4、最后通过把资源文件封装为PropertySource,加入到环境的propertySources集合中;
/**
* org.springframework.context.annotation.ConfigurationClassParser#processPropertySource
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
// 获取注解的属性值,名称、编码、特别是拿到资源的位置 - value
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 @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
// 获得PropertySourceFactory工厂,后面就可以通过工厂来创建PropertySource
// 然后加入到环境的propertySourceList配置集合中
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);
// 通过资源文件,工厂,创建PropertySource,然后加入到环境的propertySources配置集合中
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
// 异常捕获处理...
}
}
}
通过环境即可匹配读取到配置
示例
@Autowired
private Environment environment;
String propertyVal = environment.getProperty(propertyConfig);
图解
核心处理逻辑
实际上,就是通过propertySources集合,找到第一个名字相应的值,并且替换掉它的占位符后,返回出去;
进阶源码解析
入口
环境在初始化时,属性propertyResolver是会初始化为PropertySourcesPropertyResolver;
并且会传入环境的propertySources集合进去,因此我们加入进去的配置集会当做引用传入进去了;
getProperty()方法实际上是会调用PropertySourcesPropertyResolver的getProperty()
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
/**
* org.springframework.core.env.AbstractEnvironment#getProperty(java.lang.String)
*/
@Override
@Nullable
public String getProperty(String key) {
return this.propertyResolver.getProperty(key);
}
继续跟进,org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String)
/**
* org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String)
*/
@Override
@Nullable
public String getProperty(String key) {
return getProperty(key, String.class, true);
}
通过propertySources获取属性,拿到第一个并且替换掉占位符之后就返回出去
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
// 遍历propertySources集合
for (PropertySource<?> propertySource : this.propertySources) {
// 日志相关...
// 获取到对应的属性
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
// 这里如果有对应的占位符,也会被替换掉
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
// 日志相关...
return null;
}
总结
本篇主要介绍了spring如何通过内置的bean工厂后置处理器,解析配置类,识别配置类注解@PropertySources/@PropertySource;
实际上就是通过在环境enviroment中,先会存储封装好的资源对象集合propertySources,然后当从enviroment获取时,实际上就会遍历这个集合,拿到第一个匹配上key的属性配置,然后去解析它所有的占位符后,得到属性值返回;
本文深入解析Spring如何识别JVM参数、环境变量以及通过@PropertySources加载配置文件的原理。通过实例分析,展示了配置优先级:JVM参数 > 环境变量 > @PropertySources。详细探讨了Spring初始化Environment的过程,以及配置文件的读取和解析过程。



675

被折叠的 条评论
为什么被折叠?



