该系列将会非常详细地带大家阅读Spring的重要源码部分,在开始之前先简单概括一下接下来出现的类的大概作用
ApplicationContext
: 应用上下文基础接口,具备加载资源文件,获取应用内组件(Bean),监听事件,国际化的功能AbstractApplicationContext
:对ApplicationContext
做了一些最基本的实现,包括最重要的refresh()
Environment
: 表示上下文环境,持有一些环境变量,默认初始化会加入系统变量和环境变量PropertyResolver
: 属性解析器接口,具体子类实现要完成如何从PropertySources
中获取指定属性名的值PropertySource
: 表示一个属性,简单理解就是key-valuePropertySources
:PropertySource
的集合
首先我们从ClassPathXmlApplicationContext
开始,点开spring-context的test包,找到一个最简单的测试
1
2
3
4
5
6
@Test
public void testSingleConfigLocation() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(FQ_SIMPLE_CONTEXT);
assertThat(ctx.containsBean("someMessageSource")).isTrue();
ctx.close();
}
点开ClassPathXmlApplicationContext
构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
configLocation: 配置文件位置,对于ClassPathXmlApplicationContext来说是配置文件的class path,
而FileSystemXmlApplicationContext则是文件路径
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
/**
上面的构造方法会调用这个构造方法
*/
public ClassPathXmlApplicationContext(String[] configLocations,
boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
/**
调用父类构造方法,一级级点上去最终会进入AbstractApplicationContext的构造方法,方法里有一句
this.resourcePatternResolver = getResourcePatternResolver();创建了PathMatchingResourcePatternResolver,
从类名就能看出作用是正则路径资源解析器,其作用就是用来解析用ant风格描述的资源,如classpath:xxx/*.xml
*/
super(parent);
/**
* 设置并解析配置文件路径
*/
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
我们先来看一下setConfigLocations()
,为了方便理解下面的解析,先来看一个单元测试
1
2
3
4
5
6
7
8
9
10
@Test
public void testPlaceholderConfigLocation() {
/**
* 首先在系统变量里加一个key变量,现在传入context的参数为/org/springframework/context/support/${key},可以看到测试正常通过
*/
System.getProperties().setProperty("key", SIMPLE_CONTEXT);
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(PATH + "${key}");
assertThat(ctx.containsBean("someMessageSource")).isTrue();
ctx.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
AbstractRefreshableConfigApplicationContext.java, 这个类在AbstractRefreshableApplicationContext增加了对以xml为资源文件
的上下文类对xml资源文件的处理, 最关键的就是下面的方法
/**
对每个configLocation调用resolve
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
/**
getEnvironment定义在AbstractApplicationContext中,在没有设置Environment时会创建StandardEnvironment,StandardEnvironment继承了AbstractEnvironment
AbstractEnvironment主要实现了activeProfiles,也就是我们在配置文件中定义的spring.profiles.active或者运行时指定的-Dspring.profiles.active
因此resolveRequiredPlaceholders调用的是AbstractEnvironment的,
*/
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
AbstractEnvironment.java
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
/**
Environment接口也实现了PropertyResolver接口,因此在外部需要解析属性的时候都是通过Environment而不是直接调用PropertyResolver,
而Environment内部持有PropertyResolver,有点像中介者模式。
在AbstractEnvironment构造方法中创建了PropertySourcesPropertyResolver(从PropertySource集合中解析属性的类),然后调用了customizePropertySources(),
customizePropertySources是抽象方法,具体实现由子类实现。
最后这里调用的是AbstractPropertyResolver.resolveRequiredPlaceholders()。
*/
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
StandardEnvironment.java
@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()));
}
AbstractPropertyResolver.java
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
/**
辅助解析类PropertyPlaceholderHelper
* */
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
/**
this::getPropertyAsRawString是Java8的方法引用,具体实现在PropertySourcesPropertyResolver下,通过key返回value
*/
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
PropertyPlaceholderHelper.java
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
/**
具体代码就不贴了,功能就是解析出${}占位符包裹的key,通过placeholderResolver获取到value,替换原本的占位符
至此,setConfigLocation的调用就完全搞清楚了
*/
}
想不到一个setConfigLocation
都能讲这么大一篇文章,这还只是铺垫,接下来的refresh
方法才是重头戏,我们下篇再见