Spring 源码阅读笔记(一)
背景
作为Java开发程序员,不管是在工作中、和同事聊天中、面试中,都必不开的会聊一个框架:Spring,在面试中几乎都会问那么多的问题,例如Bean的生命周期,三级缓存,循环依赖这些问题。
我们都背过很多面试题,面试题背完就忘,真正记住还是得去看一遍源码,在写这篇文章之前,我保证我没有看过Spring的源码。这一系列算是领着各位看官从零开始。
我当前源码版本为 5.3.16 当前为Github上的最新的版本
下面就正式开始了。
Spring 生成Context流程概述
阅读源码前,需要找到源码的入口。
先做一个简单的spring的启动程序。
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
System.out.println(context);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.study.bean.FirstBean"></bean>
<bean class="org.springframework.study.bean.SecondBean"></bean>
</beans>
先看一下Spring启动时的全部的方法调用的流程。
看到上方的调用图,可以看出Refresh方法是关键,那么里面这么多方法,具体是做什么的呢,我也不知道。
在正式读代码之前,我们需要对我们此次设计的类进行一个认识。整理出了这样的一张图。
我们先不管这些类当中有什么方法和属性,当我们读到的时候,可以再来追加。那么我们进入第一个Spring启动时的第一个方法。
Super构造器
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent); // 初次进入时parent是null
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
看到这个方法,直接进入super的构造一探究竟
public AbstractXmlApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
当追到AbstractApplicationContext时,发生了变化,调用了自己的无参构造。
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
// 返回 ResourcePatternResolver 以用于解析位置模式进入资源实例。 默认是一个
// {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver},
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
通过这里查看呢,这个方法,弄了一个资源加载器,那么为什么ApplicationContext可以传到ResourceLoader中,通过上面的类图可以看出,AbstractContext时继承了一个DefaultResourceLoader。至于这个ResourceLoader是做什么的,我粗略的看了一下,根据他的getResources方法中,看出是获取资源文件的加载器。回到上面的代码接着往下看。
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
@Override
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
// 获取了环境信息
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
// 如果是ConfigurableEnvironment 触发合并操作
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
由于项目刚刚启动,这里传的null,到这里发现,看了个寂寞😂,可以跳过,我们大致读一下,如果说优质的情况下他会做一些什么操作。获取一下环境信息,如果是配置环境类的话,会触发一个合并操作。这里简单记一下脑子里大概有个印象。
接下来回到ClassPathXmlApplication的构造方法中。
setConfigLocations
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;
}
}
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
从上述代码看出,首先赋值了environment,后续调用了environment中的resolveRequiredPlaceholders方法。这里有一个比较重要的点。赋值environment。
这个时候会好奇this.propertyResolver是什么,在哪里赋值,(其实是我漏写了😂)
看上面的方法,createEnvironment()这个方法中会创建一个StandardEnvironment对象,new它的时候,空参构造,他继承自AbstractEnvironment,所以也会走到AbstractEnvironment的空参构造中,我们看一下他的空参构造代码。
public AbstractEnvironment() {
this(new MutablePropertySources());
}
protected AbstractEnvironment(MutablePropertySources propertySources) {
// MutablePropertySources 继承自Iterable,盲猜用来存放属性集合的。
this.propertySources = propertySources;
this.propertyResolver = createPropertyResolver(propertySources);
customizePropertySources(propertySources);
}
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
return new PropertySourcesPropertyResolver(propertySources);
}
protected void customizePropertySources(MutablePropertySources propertySources) {
}
看到这里,知道propertyResolver在哪赋值,并且知道了propertyResolver是什么类型,我们可以继续追踪 resolveRequiredPlaceholders方法了。至于customizePropertySources,看这个样子,可以看出他是一个模版方法,意思是父类定义一个方法,由子类重写,然后做一些操作,方便扩展,我们可以看一个案例。
这里有一个类,我们刚才看到有注册的,他就重写了这个方法,来看一下他做哪些扩展。
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
// systemEnvironment
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
// systemProperties
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
这个就相当于往propertySources,追加了一些配置信息。继续回到正题。
resolveRequiredPlaceholder方法究竟解析的是什么必须的占位符。
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
由于我们这里是第一次进入,所以strictHelper为null,他要去创建一个,然后赋值回来
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
这里可以看到,有三个陌生的变量,placeholderPrefix, placeholderSuffix, valueSeparator,代码追过去分别为"${"、"}"、":",看到这里终于发现比较熟悉的东西了,@Value注解里写的一些参数,是用这个包裹起来的。从此可以知道,这里是对这个值占位符做一些处理的。
setConfigLocations()方法到这里就结束了。
告一段落
后面的文章会进入refresh方法的代码阅读,由于Refresh方法里面涉及的点比较多,这篇文章先写到这。
到目前为止,我们接触到的类图,我在下面展示一下。
结束语
写文章的目的是为了帮助自己巩固知识,写的不好或者错误的地方可以在评论区指出。如果您看了文章觉得对您有所帮助可以点个赞,如果发现有些问题产生了疑惑,或者不明白的可以评论,一定知无不言。当然也希望和大家交个朋友,相互学习。