【挑战 Spring】—– Spring IOC 源码调试一

前言

使用Spring已经很久了,一直想对源码去窥探窥探,拖了又拖、等了又等。自从在Listener的第一个夜晚之后,我决定不能在等了,在等万一Spring被淘汰了呢(开个有点认真的玩笑)?所以来个挑战Spring系列,对源码细粒度调试以及解读。虽说不敢挑战全网之最细粒度跟踪,但是也是尽可能的细节了。

项目结构:

先看下调试代码,即Spring的入口,就是使用的ClassPathXmlApplicationContext类去加载配置文件

spring-iocxml.png

开始调试:

1.加载AbstractApplicationContext类的静态代码块

断点进到new ClassPathXmlApplicationContext(location)中后,第一步是加载AbstractApplicationContext类的静态代码块。这段代码,我也不不清楚作用是什么,官方注释的意思是先加载ContextClosedEvent类以避免在WebLogic 8.1中关闭应用程序时出现奇怪的类加载器问题。但是不影响主流程。( 这块也隐藏了一个小知识点就是父类的静态代码块执行顺序优先于子类的有参构造方法)

image-20200520140100819

2.执行ClassPathXmlApplicationContext构造方法

  1. 第一步会进到ClassPathXmlApplicationContext类的有参构造方法中。其中参数就是xml文件的位置classpath*:META-INF/spring-ioc.xml
1
2
3
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
  1. 第二步进入到ClassPathXmlApplicationContext另一个构造方法中,并且层层往上调用父类构造方法super(parent)【从第一步得parent参数为null】,直到调用到AbstractApplicationContext 类为止。其中调用顺:ClassPathXmlApplicationContext–>AbstractXmlApplicationContext–>AbstractRefreshableConfigApplicationContext–>AbstractRefreshableApplicationContext–>AbstractApplicationContext 这也体现了ClassPathXmlApplicationContext的一个继承关系
1
2
3
4
5
6
7
8
9
10
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
// 调用父类 AbstractApplicationContext#refresh() 的方法
refresh();
}
}

3.执行AbstractApplicationContext 类的构造方法

1
2
3
4
5
6
7
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}

实例化AbstractApplicationContext对象的时候,可以看到构造方法中执行了两个操作,第一个是getResourcePatternResolver()实例化ResourcePatternResolver;第二个是setParent(parent)

1.实例化ResourcePatternResolver对象

getResourcePatternResolver()方法中可以看到返回的对象是PathMatchingResourcePatternResolver

1
2
3
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
2.简单说一下PathMatchingResourcePatternResolver

PathMatchingResourcePatternResolver的顶级接口是ResourceLoader简单说就是来加载我们的资源文件的(比如:classpath*:META-INF/spring-ioc.xml)

3. setParent(parent)

this.parentApplicationContext parent类型的属性,此时从ClassPathXmlApplicationContext类层层调用传上来的parent参数为null。里面还有一段判断逻辑意思就是,如果parent参数不为null,还会对容器运行环境上下文对象做一个合并处理。意思就是把环境配置信息进行一个合并。

1
2
3
4
5
6
7
8
public void setParent(@Nullable ApplicationContext parent) {   
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) { getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}

到此之后代码又会回到AbstractApplicationContext 类的构造方法中

4.回到ClassPathXmlApplicationContext构造方法

super(parent)方法完成后,此时父类对象实例化完成,但是ClassPathXmlApplicationContext对象还未实例化完成。下面代码中可以看到后面还有 setConfigLocations(configLocations)refresh()两个方法。

1
2
3
4
5
6
7
8
9
10
11
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
// 调用父类 AbstractRefreshableConfigApplicationContext 的 setConfigLocations()方法
setConfigLocations(configLocations);
if (refresh) {
// 调用父类 AbstractApplicationContext#refresh() 的方法
refresh();
}
}
1.执行AbstractRefreshableConfigApplicationContextsetConfigLocations(@Nullable String... locations)

因为ClassPathXmlApplicationContext中没有setConfigLocations(configLocations)方法,该方法被封装在父类AbstractRefreshableConfigApplicationContext中,代码如下:

image-20200520151904791

在代码中可以看到有这样一段逻辑this.configLocations[i] = resolvePath(locations[i]).trim()意思就是把我们配置资源路径维护进一个数组中。下面我们说一下这个resolvePath(String path)方法。

2.执行AbstractRefreshableConfigApplicationContextresolvePath(String path)
1
2
3
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}

resolvePath(String path)方法中有两个操作,一个是getEnvironment(),一个是resolveRequiredPlaceholders(path)方法

  • getEnvironment()方法

    getEnvironment()

其中getEnvironment()方法又被封装在AbstractApplicationContext类中(如果这儿继承关系有点可以乱看下前面说ClassPathXmlApplicationContext构造方法调用顺序),在该方法中实例化了一StandardEnvironment对象并且将该对象赋值给ConfigurableEnvironment environment属性,就是容器的运行环境应用上下文对象,这个对象可以读取配置文件(SpringBoot项目中我们一般会经常用这个对象)

  • resolveRequiredPlaceholders(path)方法

    其中getEnvironment()方法我们得到了一个ConfigurableEnvironment类型对象,真正的实现类是StandardEnvironment。所以会执行StandardEnvironmentresolveRequiredPlaceholders(path)方法,而该方法有封装在了父类AbstractEnvironment中,代码如下:

    resolveRequiredPlaceholders.png

    AbstractEnvironment中我们看到又有这么一段逻辑this.propertyResolver.resolveRequiredPlaceholders(text),返回值是一个String类型(我日,真是头大的不行!),意思就是又委托给this.propertyResolver去执行了。那我们看一下这个this.propertyResolver是什么:

    AbstractEnvironment-propertyResolver.png

    可以看到是直接 new 了一个PropertySourcesPropertyResolver对象,该对象是一个资源解析器,该对象的顶级接口是PropertyResolver。我理解的就是用来解析Properties配置文件的,里面定义了很多读取配置文件的API。

    那好代码接着往下走可以看this.propertyResolver.resolveRequiredPlaceholders(text)到返回值就是我们配置的资源文件路径

    propertyResolver-resolveRequriedPlaceholders.png

3.回到AbstractRefreshableConfigApplicationContextsetConfigLocations(@Nullable String... locations)方法

因为只配了一个资源文件,所以this.configLocations[i] = resolvePath(locations[i]).trim()这段代码执行完后,this.configLocations的长度为1,并且里面的值为我们资源文件的路径

image-20200520151904791

4.再次回到ClassPathXmlApplicationContext构造方法

因为AbstractRefreshableConfigApplicationContextsetConfigLocations(@Nullable String... locations)方法是在ClassPathXmlApplicationContext类的构造方法调用的,所以执行完之后再次回到ClassPathXmlApplicationContext的构造方法中。代码如下:

1
2
3
4
5
6
7
8
9
10
11
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
// 调用父类 AbstractRefreshableConfigApplicationContext 的 setConfigLocations()方法
setConfigLocations(configLocations);
if (refresh) {
// 调用父类 AbstractApplicationContext#refresh() 的方法
refresh();
}
}

这时候往下执行就是大名鼎鼎的refresh()方法由于篇幅原因,refresh()方法在下一篇博客中更新。

欢迎扫码关注

如果喜欢请关注我公众号【程序倾听者】,说出你的故事!我在这里倾听!

顶我一下下!
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2021 ListenerSun
  • 访问人数: | 浏览次数:

请我吃个棒棒糖可否~

支付宝
微信