Spring 源码阅读笔记(一)

1,531 阅读5分钟

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启动时的全部的方法调用的流程。

2022-03-17-17-11-02-image.png 看到上方的调用图,可以看出Refresh方法是关键,那么里面这么多方法,具体是做什么的呢,我也不知道。

在正式读代码之前,我们需要对我们此次设计的类进行一个认识。整理出了这样的一张图。

2022-03-18-13-11-38-image.png

我们先不管这些类当中有什么方法和属性,当我们读到的时候,可以再来追加。那么我们进入第一个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,看这个样子,可以看出他是一个模版方法,意思是父类定义一个方法,由子类重写,然后做一些操作,方便扩展,我们可以看一个案例。

image.png 这里有一个类,我们刚才看到有注册的,他就重写了这个方法,来看一下他做哪些扩展。

@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方法里面涉及的点比较多,这篇文章先写到这。
到目前为止,我们接触到的类图,我在下面展示一下。

image.png

结束语

写文章的目的是为了帮助自己巩固知识,写的不好或者错误的地方可以在评论区指出。如果您看了文章觉得对您有所帮助可以点个赞,如果发现有些问题产生了疑惑,或者不明白的可以评论,一定知无不言。当然也希望和大家交个朋友,相互学习。