IoC源码分析篇
参考博客:https://www.cnblogs.com/ITtangtang/articles/3978349.html#a3
xsd学习:https://www.w3school.com.cn/schema/schema_simple_attributes.asp
dtd学习:
https://www.w3school.com.cn/dtd/dtd_intro.asp
注意:本文基于spring-5.0.4.RELEASE版本源码的研究,本文叙述过程比较口水化,敬请包涵、谅解。
1、正文开始
spring官网文档:
https://docs.spring.io/spring/docs/5.0.4.RELEASE/spring-framework-reference/core.html#beans
研究Spring可以结合官网文档,学习效率可能加倍,不过要求你英文水平过关。废话少说,下文进行主题。
首先,我们看源码结构:(不知道如何下载源码并导入IDEA的小伙伴,可以参考Spring源码分析(1)–准备篇)
我们从源码看出,spring项目由多个子模块组成,如:
spring-beans:主要放置BeanFactory、FactoryBean、DefaultListableBeanFactory、InitializingBean、BeanWrapper等,IoC将依赖该模块
spring-context:主要有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、ApplicationContext、ApplicationContextAware等构成IoC容器基础的上下文
spring-core:spring的核心包,包括一些基础工具,如cglib、断言Assert、Base64Utils等,并提供annotation、convert的支持。
spring-aop:aop模块
spring-web:http相关一些工具类,在spring集成其他框架技术的时候会依赖该模块,如redistemplate等
spring-webmvc:springmvc模块
2、Spring IoC源码分析
源码工程好像没有所谓的spring-ioc?那么我要看IoC要哪里入手呢?
不知道大家还记不记得写过的Spring的hello world的代码,不记得,没关系,下面的代码我们一起回忆一波!
找到spring-test工程,创建Test类,然后输入spring入门hello world代码:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
Object bean = context.getBean(Bea.class);
}
}
application-context.xml配置文件:
现在有印象了吧,但之前我们只知道,写配置文件配置好我们需要的Bean,然后把配置文件交给Spring容器,它就可以帮我们自动构建我们需要的Bean对象。但是这个所谓的“自动”过程是怎样的呢?下面开始IoC源码的讲解,请人手一份spring-5.0.4.RELEASE版本的源码,跟着我的思路一起走。
2.1、IoC容器的初始化
时序图大图查看下载地址:https://www.processon.com/view/5d8a3140e4b011ca2aad49a1
时序图看第一遍不懂没关系,后面总结还会提到。
Spring IoC初始化分三部曲:定位、加载、注册,相信从别的博客,或者学习视频都能听到这几个名词,站在前辈的肩膀上学习,总能看得更远,更清晰,本文也是从其他博客学习借鉴过来的。下面我们带着对三部曲的疑惑,进入初始化过程:
2.1.1、配置资源的定位
点进Test.java的ClassPathXmlApplicationContext构造器,进入带String参数的构造器
ClassPathXmlApplicationContext:
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {
configLocation}, true, null);
}
接下来进入this(new String[] {configLocation}, true, null);
如果你使用的IDE是IntelliJ IDEA,可以将光标放在this上,然后Ctrl + Alt + B 进入对应的实现,该方式的好处是:当调用接口或者抽象的抽象方法的时候,能直接进入对应的实现方法。
ClassPathXmlApplicationContext:
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
//设置传入进来的父容器、创建Bean资源解析器
super(parent); // 代码1
//将传入进来的配置文件路径(如spring/aaa.xml,spring/bbb.xml)保存起来
setConfigLocations(configLocations); // 代码2
if (refresh) {
// 初始化最核心的方法,就是refresh()
// 刷新过程将会将传入的资源(配置文件等)转换为BeanDefinition
// 然后根据BeanDefinition生成Bean,再触发DI注入操作,为Bean的属性赋值
// 主线
refresh(); // 代码3
}
}
注:阅读源码一定切忌陷入细节当中,导致头脑混乱。以下过程仅列出主线代码,分支代码详情可以点对应提示的链接观看。
上述代码分三步走,接下来介绍具体的功能:
代码1,调用父类AbstractApplicationContext的构造器,创建一个资源解析器,并将传递进来的parent容器保存起来(如果父容器不为空),然后将父容器的环境合并到当前容器环境中去。
详情可看:super(parent)
代码2,调用父类AbstractRefreshableConfigApplicationContext实现的setConfigLocations(String… locations)方法,将参数"application-context.xml"保存到AbstractRefreshableConfigApplicationContext的成员变量中,该成员变量将在代码3中提到如何使用,先做个铺垫。
详情可看:setConfigLocations(configLocations)
代码3,这一步是初始化过程最重要的一步,本文接下来将大概(将忽略不重要的步骤)介绍初始化的刷新过程。请根据上文提供的时序图,一起来看看refresh执行了哪些操作。
按Ctrl + Alt + B 进入refresh对应实现:
由AbstractApplicationContext提供对应的refresh()方法的实现:
AbstractApplicationContext:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
//获取一个可刷新的BeanFactory,里面包括了一个抽象方法refreshBeanFactory();
//用来执行子类的刷新,由子类实现
//主线
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为BeanFactory配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
//为容器的某些子类指定特殊的BeanPost事件处理器
postProcessBeanFactory(beanFactory);
//调用所有注册的BeanFactoryPostProcessor的Bean
invokeBeanFactoryPostProcessors(beanFactory);
//为BeanFactory注册BeanPost事件处理器
//BeanPostProcessor是Bean后置处理器,用于监听容器出发的事件
registerBeanPostProcessors(beanFactory);
//初始化信息源,和国际化有关。
initMessageSource();
//初始化容器事件传播器
initApplicationEventMulticaster();
//调用子类的某些特殊Bean初始化方法
onRefresh();
//为事件传播器注册事件监听器
registerListeners();
//重点 重点 重点
//初始化所有的设置lazy-init=false的bean
finishBeanFactoryInitialization(beanFactory); //代码4
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
...
}
finally {
...
}
}
}
AbstractApplicationContext的refresh()是一个模板方法,我们接下来进入“主线”来看接下来如何执行实际上的refresh功能。
其中代码4 ,将触发依赖注入,后续会详细讲述。DI入口1
AbstractApplicationContext:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//调用由子类提供的刷新BeanFactory的方法
//主线
refreshBeanFactory();
//调用子类提供的,获取BeanFactory的方法
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
obtainFreshBeanFactory()方法使用了委派模式,本身自己不做刷新功能,而是将实际刷新任务委派给子类
AbstractRefreshableApplicationContext的refreshBeanFactory()方法。我们从命名其实可以看出来,带有Refreshable的单词AbstractRefreshableApplicationContext类实际上,提供了刷新功能的实现。
按Ctrl + Alt + B 进入refreshBeanFactory方法对应实现,进入AbstractRefreshableApplicationContext的实现:
AbstractRefreshableApplicationContext:
protected final void refreshBeanFactory() throws BeansException {
//假如存在BeanFactory,则销毁、关闭它
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建新的BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//加载bean定义
//主线
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
...
}
}
在AbstractRefreshableApplicationContext的refreshBeanFactory()方法中,主要做了四件事:
1)销毁关闭存在的BeanFactory
2)创建一个新的BeanFactory,其对应实现为DefaultListableBeanFactory
这里开始,BeanFactory开始登场,首先我们先查看下createBeanFactory方法对应的实现:
AbstractRefreshableApplicationContext:
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
AbstractRefreshableApplicationContext的createBeanFactory方法事实上创建的默认BeanFactory是DefaultListableBeanFactory,下面我们看下DefaultListableBeanFactory与BeanFactory的关系图:
由图可看出,BeanFactory有三个子接口ListableBeanFactory、AutowireCapableBeanFactory、HierarchicalBeanFactory,分别代表的功能为ListableBeanFactory表示 Bean 是可列表的、AutowireCapableBeanFactory表示Bean 是可自动装配的、HierarchicalBeanFactory 表示Bean 是有继承关系的,也就是每个Bean 有可能有父 Bean。而DefaultListableBeanFactory实现了上述接口的全部功能。
3)进行Spring IoC初始化三步曲的加载步骤。(接下来2.1.2重点介绍)
4)将其创建好并自定义化后的DefaultListableBeanFactory保存到AbstractRefreshableApplicationContext的成员变量域中,为AbstractRefreshableApplicationContext的getBeanFactory提供值。当前先做铺垫,后面会介绍如何使用。
2.1.2、加载资源文件
按Ctrl + Alt + B进入loadBeanDefinitions(beanFactory)对应的实现,AbstractXmlApplicationContext提供如下实现:
AbstractXmlApplicationContext:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建一个Xml的BeanDefinition的读取器
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//为BeanDefinition读取器配置当前上下文的资源加载环境
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//允许子类提供BeanDefinition的自定义初始化功能
initBeanDefinitionReader(beanDefinitionReader);
//执行事实上的加载
//主线
loadBeanDefinitions(beanDefinitionReader);
}
进入主线loadBeanDefinitions方法:
AbstractXmlApplicationContext:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//如果存在Resource资源配置,则进行加载
//当前获取的返回值为null,由于没有使用到构造器
//ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)
//因此没有为Resource[] configResources赋值
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//getConfigLocations()返回ClassPathXMLApplicationContext第二步
//setConfigLocations(configLocations)设置进来的xml配置文件的路径(即application-context.xml的路径)
// 由AbstractRefreshableConfigApplicationContext提供具体实现
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//主线
//以下步骤将会把configLocation转化为Resource,其实最终调用的还是reader.loadBeanDefinitions(configResources)的方法
reader.loadBeanDefinitions(configLocations);
}
}
接下来需要重点介绍一下XmlBeanDefinitionReader,我们先看下其继承哪些父类,实现了哪些接口:
由上图可看出,XmlBeanDefinitionReader继承了AbstractBeanDefinitionReader抽象类,并实现了BeanDefinitionReader接口。事实上,XmlBeanDefinitionReader只实现了int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException方法。其他定义在BeanDefinitionReader接口的抽象方法都由AbstractBeanDefinitionReader提供实现。接下来我们来看看XmlBeanDefinitionReader是如何实现加载BeanDefinition的。
按Ctrl + Alt + B进入reader.loadBeanDefinitions(configLocations),该方法由AbstractBeanDefinitionReader提供对应实现:
AbstractBeanDefinitionReader:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
以上步骤加载的资源配置,还是本地资源的配置,该配置并不能让Spring解析识别,在接下来的步骤,将会统一转换为Resource对象:
AbstractBeanDefinitionReader:
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取资源加载器,该资源加载器事实上是ClassPathXmlApplicationContext对象,从哪里得知呢?
ResourceLoader resourceLoader = getResourceLoader(); //代码5
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//由于ClassPathXmlApplicationContext实现ResourcePatternResolver,将执行以下分支
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//将字符串类型的配置文件转换为Spring能识别的Resource对象资源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);//代码6
//进入重载的loadBeanDefinitions(Resource..)方法
//最终调用由XmlBeanDefinitionReader实现的loadBeanDefinitions(Resource resource)方法
//主线
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
...
return loadCount;
}
catch (IOException ex) {
...
}
}
else {
//仅可以从绝对路径加载单独的resource
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
...
return loadCount;
}
}
代码5:getResourceLoader()返回由AbstractXmlApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中beanDefinitionReader.setResourceLoader(this)设置进来的ClassPathXmlApplicationContext对象
详情可参考:getResourceLoader()
代码6:
((ResourcePatternResolver) resourceLoader).getResources(location)为AbstractApplicationContext实现的getResources方法。此时即调用ClassPathXmlApplicationContext对象的getResources方法,其由父类AbstractApplicationContext提供实现,返回值为ClassPathXmlApplicationContext代码1 super(parent)初始化时,调用AbstractApplicationContext()方法创建的PathMatchingResourcePatternResolver对象。
详情可参考:((ResourcePatternResolver) resourceLoader).getResources(location)
继续看主线,主线调用AbstractBeanDefinitionReader重载的loadBeanDefinitions(Resource… resources)方法:
AbstractBeanDefinitionReader:
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
//主线
counter += loadBeanDefinitions(resource);
}
return counter;
}
接着调用抽象方法loadBeanDefinitions(Resource resource),由XmlBeanDefinitionReader提供实际上的实现:
XmlBeanDefinitionReader:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
...
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null