本文首发于微信公众号【WriteOnRead】,欢送关注。

概述

上篇文章「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 的整体继承构造比较复杂,为了便于剖析其外围实现,这里先临时疏忽它实现的接口,只看它的类继承构造:

后面两个步骤的代码不再深入分析,这里间接进入第三步,也就是 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 容器的源码了,次要找到了一条主线,为便于对整体有个理解,这里简略总结了一个思维导图(还有前面的预报):

小伙伴们读的时候也能够用思维导图工具画一下次要流程,起到提纲挈领的作用,也便于当前回顾。

源码读起来还是须要一点急躁的,毕竟它就是这么朴实无华……且干燥????