8

Spring IoC 容器初始化

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzU4NzYyMDE4MQ%3D%3D&%3Bmid=2247484606&%3Bidx=1&%3Bsn=ddbb3ac11b22610f3c7d6e5db6b58a6f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

概述

上篇文章「Spring 中的 IoC 容器」从整体介绍了 Spring IoC 容器的相关概念和大致实现流程,本文要进入源码来一探究竟了。

这里仍以前文的代码为例进行分析,测试代码如下:

public class IocTests {
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-ioc.xml");
System.out.println(context.getBean("person"));
System.out.println(context.getBean("dog"));
}
}

/*
* 输出结果:
* Person{id=12, name='Jack-12'}
* Dog{age=1}
*/

PS: 此处 Spring Framework 版本为 5.2.12.RELEASE,其他版本可能略有不同。

代码分析

从 ClassPathXmlApplicationContext 的构造器进入它的代码。

ClassPathXmlApplicationContext 有很多重载的构造器,不过多数都会调用到下面这个:

public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)

throws BeansException
{

// 调用父类构造器,保存传入的父容器
super(parent);

// 保存配置文件信息
setConfigLocations(configLocations);

// 刷新 IoC 容器
if (refresh) {
refresh();
}
}

该构造器主要做了三件事:

  1. 调用父类的构造器,保存传入的父容器

  2. 保存配置信息,在本例中就是 application-ioc.xml

  3. 刷新 IoC 容器

其中最核心的就是第三步,也是最复杂的。

由于 ClassPathXmlApplicationContext 的整体继承结构比较复杂,为了便于分析其核心实现,这里先暂时忽略它实现的接口,只看它的类继承结构:

JFnmee.png!mobile

前面两个步骤的代码不再深入分析,这里直接进入第三步,也就是 refresh 方法(该方法是在 AbstractApplicationContext 类中实现的):

public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext
{
// ...

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// 获取 BeanFactory
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

// catch ...
// finally ...
}
}

// ...

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
}

refresh 方法里面封装了很多方法,每个方法里面又是一大堆代码……

刚开始看到这里可能会被吓到(我当初就是这样被劝退的)。

其实不必,代码虽然很多,但读起来还是有迹可循的:就是要先找到一条主线。就像一棵树,先找到树干,其它的都是细枝末节。先主后次,要不很容易陷进去、读起来一团乱麻。

PS: 之前写过一篇如何阅读 JDK 源码的文章「 我是如何阅读JDK源码的? 」,有兴趣可以参考一下。

但二者又有些不同:JDK 源码相对独立,一般关联性不大,而 Spring 的代码前后关联太多。

这里的主线是什么呢?

就是 obtainFreshBeanFactory 方法(它的实现在 AbstractRefreshableApplicationContext 类中),如下:

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
// ...

@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果容器已经存在,则销毁容器中的对象、关闭容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建 BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 加载 BeanDefinition
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

// ...

protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException
;
}

refreshBeanFactory 看名字可以推测是刷新 IoC 容器,它主要做了三件事:

  1. 如果 IoC 容器已经存在,则销毁容器中的对象、关闭容器(正如其名,refresh,是一个全新的,就要先把旧的干掉)。

  2. 创建 BeanFactory,即 DefaultListableBeanFactory,它就是 Spring IoC 容器的默认实现。

  3. 加载 BeanDefinition,也就是从配置文件(application-ioc.xml)中加载我们定义的 bean 信息,这一步也是最复杂的。

这里的 loadBeanDefinitions 是一个抽象方法?!

是的。这就是设计模式中的「模板模式(Template Pattern)」,这个模式不难理解,不了解也不影响,有空可以再看。

JDK 中的 AbstractQueuedSynchronizer(AQS) 也使用了模板模式,前文「 JDK源码分析-AbstractQueuedSynchronizer(2) 」可以参考。

loadBeanDefinitions 方法其实就是把实现代码放在子类中了。

What?它的子类有那么多,哪个才是真·儿子呢?

还记得上面那个类的继承结构图吗?

所以,这里它的子类实现指的就是 AbstractXmlApplicationContext#loadBeanDefinitions 方法:

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
// ...

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建 BeanDefinitionReader,用于读取 BeanDefinition
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

// ...

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 读取 Resource 类型的配置
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 读取 String 类型的配置
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
}

它来了,XmlBeanDefinitionReader,是用来读取 BeanDefinition 的。

这里 BeanDefinition 的类型有两种:Resource 和 String。其实本质上还是一种,即 Resource,String 最终还是要转为 Resource。

这两个 loadBeanDefinitions 最终都会调用 XmlBeanDefinitionReader#loadBeanDefinitions 方法:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
// ...

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}

Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}

// 读取配置文件
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 实际加载 BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
// catch ...
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException
{

try {
// 将 Resource 解析为 Document 对象
Document doc = doLoadDocument(inputSource, resource);
// 从 Document 解析和注册 BeanDefinition
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
// catch ...
}

// ...
}

loadBeanDefinitions 做了层封装,主要工作是在 doLoadBeanDefinitions 实现的。doLoadBeanDefinitions 方法主要做了两件事:

  1. 将 Resource 解析为 Document 对象。

  2. 从 Document 解析和注册 BeanDefinition。

第一步只是工具人,不必深究。

关键在于第二步,也比较复杂,后文再分析吧,毕竟源码读起来还是有点累……本文就先到这了。

Spring 源码中的关键部分通常是有点小规律的,比如:

  1. 以 try...catch 包围

  2. 以 do 开头的方法才是实际做事的,前面都是 caller,比如 doLoadBeanDefinitions、doGetBean 等

其他规律小伙伴们可以继续补充。

小结

本文开始进入 Spring IoC 容器的源码了,主要找到了一条主线,为便于对整体有个了解,这里简单总结了一个思维导图(还有后面的预告):

Y7f6jmb.png!mobile

小伙伴们读的时候也可以用思维导图工具画一下主要流程,起到提纲挈领的作用,也便于以后回顾。

源码读起来还是需要一点耐心的,毕竟它就是这么朴实无华……且枯燥

qEFbQjE.gif!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK