注:本章内容主要是作为springboot的run()方法中的环境预准备prepareEnvironment()方法的预备知识,其中本章的environment部分可能比较抽象,不是很好理解,这并没有什么问题,只要大概明白即可,后续阅读prepareEnvironment()方法时会加深理解。
1 @PropertySource
1.1 使用
PropertySource是spring体系用于存放key-value的property属性的基本单位
最常见的使用方法是与@Value搭配:
@Component
@PropertySource("classpath:db.properties")
public class DBConnection {
@Value("${db.driver}")
private String driverClass;
@Value("${db.url}")
private String dbUrl;
@Value("${db.username}")
private String userName;
@Value("${db.password}")
private String password;
public DBConnection(){}
public void printDBConfigs(){
System.out.println("Db Driver Class = " + driverClass);
System.out.println("Db url = " + dbUrl);
System.out.println("Db username = " + userName);
System.out.println("Db password = " + password);
}
}
db.properties:
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/test
db.username=cxuan
db.password=111111
main函数:
public static void main(String[] args){
ApplicationContext context = new AnnotationConfigApplicationContext(DBConnection.class);
DBConnection bean = context.getBean(DBConnection.class);
bean.printDBConfigs();
}
1.2 原理
最重要的后置处理器ConfigurationClassPostProcessor会对@Configuration等注释类进行解析,这其中就包含了@PropertySource类,该类的解析具体在doProcessConfigurationClass()方法的第二步中:
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// 1、首先处理内部类,处理内部类时,最终还是调用doProcessConfigurationClass()方法
processMemberClasses(configClass, sourceClass);
// 2、处理属性资源文件,加了@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
}
...
}
可以看到,解析@PropertySource调用的是processPropertySource():
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
//取出该注释的name:对于DBConnection类,name为“”(没有设置)
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
//..获取encoding,代码略,同样为空
//获取注释的value,对于DBConnection类,是"classpath:db.properties"
String[] locations = propertySource.getStringArray("value");
//..略
//获取执行工厂,默认为PropertySourceFactory
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
//递归locations中的所有文件,每个文件都生成Resource类,
//然后调用factory.createPropertySource()生成PropertySource类,再添加到environment.propertySources中。
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)));
}
//...略
}
可以看到,@PropertySource注释的类,经过解析后,会转化成PropertySource类,并在最后存入environment的propertySources中。
而关于Environment、PropertySource和propertySources的内容,后续会进行详细介绍。
2 PropertySource
2.1 PropertySource
PropertySource是一个用于存储key和value的类,用于存储spring环境中的配置信息,例如application.properties或application.yml就是用的这个类。
代码:
public abstract class PropertySource<T> {
protected final Log logger;
//key
protected final String name;
//value
protected final T source;
public PropertySource(String name, T source) {
this.logger = LogFactory.getLog(this.getClass());
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
对于application.properties, 每一行数据对应着一个PropertySource。
2.2 PropertySources
PropertySources是存储PropertySource的通用接口,从下面的代码中可以看到,其继承了Iterable<PropertySource<?>>
类,这样就具有了迭代PropertySource的功能。
public interface PropertySources extends Iterable<PropertySource<?>> {
boolean contains(String var1);
PropertySource<?> get(String var1);
}
MutablePropertySources实现了PropertySources接口,使用了List中的CopyOnWriteArrayList来存储PropertySource:
public class MutablePropertySources implements PropertySources {
private final Log logger;
private final List<PropertySource<?>> propertySourceList;
public MutablePropertySources() {
this.propertySourceList = new CopyOnWriteArrayList();
this.logger = LogFactory.getLog(this.getClass());
}
//...略
}
3 Environment
Environment是spring体系中,用于存储配置信息的接口/类。
实际上,Environment=profiles+properties
3.1 environment和PropertySources
AbstractEnvironment类将MutablePropertySources类型的propertySources作为成员变量,因此AbstractEnvironment及子类都有了propertySources:
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
...
private final MutablePropertySources propertySources;
...
}
因此,environment中可以存入多个propertySource。
3.2 environment体系
关于Environment,传送门:
Spring源码系列 — Envoriment组件(只需要了解每个类大概是干什么的就行,不需要太细致)。
因为上面的文章中没有对StandardServletEnvironment进行讲解,因此在这里补充:
3.3 StandardServletEnvironment
StandardServletEnvironment相对于父类StandardEnvironment,会再设置两个PropertySource:
- servletConfigInitParams
- servletContextInitParams
这里不对这两个PropertySource进行讲解,以后用到时再去研究。
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
public StandardServletEnvironment() {
}
//构造该类时会调用customizePropertySources()
protected void customizePropertySources(MutablePropertySources propertySources) {
//配置servletConfigInitParams
propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
//配置servletContextInitParams
propertySources.addLast(new StubPropertySource("servletContextInitParams"));
//配置JNDI,这里会用到SpringProperties类,略
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource("jndiProperties"));
}
super.customizePropertySources(propertySources);
}
}