Spring源码(1) 从ClassPathXmlApplicationContext开始

Posted by zsh on September 14, 2021

该系列将会非常详细地带大家阅读Spring的重要源码部分,在开始之前先简单概括一下接下来出现的类的大概作用

  • ApplicationContext: 应用上下文基础接口,具备加载资源文件,获取应用内组件(Bean),监听事件,国际化的功能
  • AbstractApplicationContext: 对ApplicationContext做了一些最基本的实现,包括最重要的refresh()
  • Environment: 表示上下文环境,持有一些环境变量,默认初始化会加入系统变量和环境变量
  • PropertyResolver: 属性解析器接口,具体子类实现要完成如何从PropertySources中获取指定属性名的值
  • PropertySource: 表示一个属性,简单理解就是key-value
  • PropertySources: 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方法才是重头戏,我们下篇再见