购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3 Spring IoC实现原理解析

本节将介绍Spring IoC容器是如何实现的。这是一个非常庞大的问题,会涉及很多Spring底层代码的解析。但是任何复杂的框架,其底层原理都是很简单的。为了能使读者明白代码的实现,下面将一些Spring IoC中重要的概念提炼出来。

2.3.1 BeanFactory代码解析

这是整个IoC容器最顶层接口,其定义了一个IoC容器的基本规范,以下是Spring BeanFactory的接口定义。限于篇幅原因,删除了大量的代码注释。BeanFactory是一个低配版的IoC容器,其定义了IoC容器基本的功能。BeanFactory具体代码如下:

2.3.2 ApplicationContext代码解析

相比于低配版的IoC容器BeanFactory,ApplicationContext是高配版的IoC容器了。从ApplicationContext接口的定义可以看出,其在原有的BeanFactory接口上实现了更加复杂的扩展功能。ApplicationContext代码如下:

上面ApplicationContext接口的定义并不能很直观地反应出ApplicationContext接口对BeanFactory接口的扩展。如图2-7给出了更加直观的说明。

图2-7 BeanFactory和ApplicationContext继承关系

从图2-7可以看到,ListableBeanFactory和HierarchicalBeanFactory这两个接口都继承了BeanFactory,ApplicationContext则继承了这两个接口,可以证明ApplicationContext接口是扩展了BeanFactory接口的。

2.3.3 BeanDefinition代码解析

由于BeanDefinition代码比较长,限于篇幅,此处只截取部分代码如下:

熟悉Spring配置的读者都知道,对于每个JavaBean,都有各自的类名、属性、类型和是否单例等信息。Spring是如何管理这些JavaBean信息的呢?其实Spring就是通过BeanDefinition来管理各种JavaBean及JavaBean相互之间的依赖关系的。BeanDefinition抽象了开发人员对JavaBean的定义,Spring将开发人员定义的JavaBean的数据结构转化为内存中的BeanDefinition数据结构进行维护。

2.3.4 Spring IoC代码分析

由于Spring IoC的容器实现很多,这里通过图2-8所示的其中一个标准的IoC容器FileSystemXmlApplicationContext为例,分析整个IoC容器的启动过程。

整个IoC容器的启动可以概括为以下两步。

(1)创建BeanFactory。

(2)实例化Bean对象。

图2-8 各容器类图

下面将对以上两步做具体的分析。为了能更加直观地查看IoC启动过程,本书通过一个简单的示例代码,一步步走进Spring底层代码。创建一个出版社类PressServiceImpl和图书类BookServiceImpl,PressServiceImpl依赖BookServiceImpl,PressServiceImpl类源代码如下:

BookServiceImpl类如下所示:

本例用一个最简单最基础的通过XML配置的方式,阐述IoC容器的启动过程,PressServiceImpl和BookServiceImpl两者依赖关系配置如下:

通过简单的测试代码,从Spring IoC容器中获取出版社对象,然后打印图书的价格。测试代码如下:

在SourceCodeLearning类中设置好断点后,下面将一步步进入Spring底层代码。首先从如下代码行开始进入Spring代码:

断点跟踪进入到FileSystemXmlApplicationContext的构造器中后,会发现其最终调用的是如下构造器:

通过FileSystemXmlApplicationContext跟踪上述构造器可以发现,其主要完成了以下三个步骤:

(1)初始化父容器AbstractApplicationContext。

(2)设置资源文件的位置setConfigLocations。

(3)使用核心方法refresh(),其实是在超类AbstractApplicationContext中定义的一个模板方法(模板方法设计模式参见附录)。

下面将重点介绍其核心方法refresh()。

首先找到refresh()方法的定义——ConfigurableApplicationContext接口中定义了该方法。方法定义如下:

     void refresh() throws BeansException, IllegalStateException;

由图2-8可知,ConfigurableApplicationContext的基类是BeanFactory。

AbstractApplicationContext类实现了ConfigurableApplicationContext接口,重写了refresh()方法。下面将分析FileSystemXmlApplicationContext类的超类AbstractApplicationContext中的refresh()方法,由于方法很长,只截取了部分重要代码:

AbstractApplicationContext.refresh()方法是个模板方法,定义了需要执行的一些步骤。并不是实现了所有的逻辑,只是充当了一个模板,由其子类去实现更多个性化的逻辑。

模板方法refresh()中最核心的两步如下。

(1)创建BeanFactory:

     ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

(2)实例化Bean:

     finishBeanFactoryInitialization(beanFactory);
1. 创建BeanFactory

创建BeanFactory重点分析AbstractApplicationContext.obtainFreshBeanFactory()方法。以下是AbstractApplicationContext.obtainFreshBeanFactory()方法的实现:

从以上代码可以发现,AbstractApplicationContext.obtainFreshBeanFactory()方法分为以下两步:

(1)刷新BeanFactory,即refreshBeanFactory()。

(2)获取BeanFactory,即getBeanFactory()。

这两步骤中刷新BeanFactory的方法refreshBeanFactory()是核心,接下来进一步分析refreshBeanFactory()方法。其方法定义如下:

这个方法的定义是在AbstractApplicationContext中,是一个抽象方法,也是一个模版方法,需要AbstractApplicationContext的子类来实现逻辑。其具体实现是在其子类AbstractRefreshableApplicationContext中完成的。refreshBeanFactory()方法实现的部分代码如下:

可以发现,在refreshBeanFactory()方法的实现中,首先检查当前上下文是否已经存在BeanFactory。如果已经存在BeanFactory,先销毁Bean和BeanFactory,然后创建新的BeanFactory。

DefaultListableBeanFactory beanFactory = createBeanFactory();这行代码只是创建了一个空的BeanFactory,其中没有任何Bean。因此refreshBeanFactory()方法的核心功能是在loadBeanDefinitions(beanFactory);这行代码中实现的。进入loadBeanDefinitions(beanFactory)方法进行分析。

首先来看loadBeanDefinitions方法的定义:

此方法是抽象方法,需要其子类实现。其具体实现是在AbstractXmlApplicationContext类中。其方法实现如下所示:

loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,通过上一步创建的空的BeanFactory来创建一个XmlBeanDefinitionReader对象。XmlBeanDefinitionReader是用来解析XML中定义的bean的。下面重点讲解loadBeanDefinitions(beanDefinitionReader)方法,这是一个重载的方法,这个方法的入参是刚刚生成的XmlBeanDefinitionReader对象。下面将进入重载的loadBeanDefinitions方法进行分析,代码如下:

这个方法主要功能是解析资源文件的位置,然后调用XmlBeanDefinitionReader对象的loadBeanDefinitions方法解析Bean的定义。

下面将对reader.loadBeanDefinitions(configLocations);这段代码进行分析。如图2-9所示,XmlBeanDefinitionReader是AbstractBeanDefinitionReader的子类,所以reader.loadBeanDefinitions(configLocations)会调用其父类的方法loadBeanDefinitions。

图2-9 XmlBeanDefinitionReader相关的类图

下面将分析AbstractBeanDefinitionReader的方法loadBeanDefinitions,其方法实现如下:

可以发现loadBeanDefinitions(String... locations)方法会遍历资源数组,最终会调用重载方法loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources),重载方法的部分实现代码如下:

这个方法会解析资源文件的路径,得到Resource[]资源数组,核心逻辑是调用loadBeanDefinitions(resource)方法,进入这个方法查看其代码如下:

loadBeanDefinitions内部工作原理是遍历每个资源,依次调用loadBeanDefinitions(Resource resource)重载的方法。该重载的方法定义在顶层接口BeanDefinitionReader中,其方法定义如下:

如图2-9所示,loadBeanDefinitions方法的实现是在XmlBeanDefinitionReader中。具体实现如下:

该方法实现会调用重载方法loadBeanDefinitions(EncodedResource encodedResource)。因该重载方法很长,现截取部分代码如下:

loadBeanDefinitions(EncodedResource encodedResource)方法以流的方式读取资源文件,调用doLoadBeanDefinitions()方法。doLoadBeanDefinitions()是载入定义Bean的核心方法。进入doLoadBeanDefinitions()方法,查看其部分代码如下:

从doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法的定义可以看出,最终注册Bean的地方是在registerBeanDefinitions(doc, resource);这一行代码。进入registerBeanDefinitions(doc, resource)方法,查看其代码如下:

registerBeanDefinitions(Document doc, Resource resource)方法的核心逻辑是在documentReader.registerBeanDefinitions(doc, createReaderContext(resource));这一行,这里发生了对Bean的注册。进入registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法查看其方法实现如下:

registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法是在DefaultBeanDefinitionDocumentReader中实现的。核心是通过doRegisterBeanDefinitions()方法实现的。进入doRegisterBeanDefinitions(Element root)方法的代码,查看其部分实现代码如下:

doRegisterBeanDefinitions(Element root)方法的核心逻辑在parseBeanDefinitions(root,this.delegate);这个方法中处理。进入parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法的代码,查看其方法部分实现如下:

parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法的核心逻辑是依赖parseDefaultElement(ele, delegate);方法实现的,进入parseDefaultElement(Element ele,BeanDefinitionParserDelegate delegate)方法的代码,查看其代码如下:

根据不同bean的配置不同,进入不同分支执行。本书的示例是进入processBeanDefinition(ele,delegate)方法,下面进入processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法,查看其实现如下:

从processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法的代码可知,最关键的是BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry());的调用。这是注册Bean的关键代码,查看其代码如下:

registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());这一行是将Bean的名字和BeanDefinition对象进行注册的地方。该方法的定义是在BeanDefinitionRegistry中。其定义如下:

     void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
     throws BeanDefinitionStoreException;

本例将进入BeanDefinitionRegistry接口的实现类DefaultListableBeanFactory中,查看该方法,部分实现代码如下:

从registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法代码可以看出,先从beanDefinitionMap这个ConcurrentHashMap对象根据beanName查找是否已经有同名的bean,如果不存在,则会调用beanDefinitionMap.put(beanName, beanDefinition)方法,以beanName为key,beanDefinition为value注册,将这个Bean注册到BeanFactory中,并将所有的BeanName保存到beanDefinitionNames这个ArrayList中。

到此,完成了IoC第一部分——创建BeanFactory的代码解析。但是,此时Bean只是完成了Bean名称和BeanDefinition对象的注册,并没有实现Bean的实例化和依赖注入。下面将要分析IoC的第二个关键部分Bean的初始化。

2. 实例化Bean

在创建BeanFactory的过程中,BeanDefinition注册到了BeanFactory中的一个ConcurrentHashMap对象中了,并且以BeanName为key,BeanDefinition为value注册。下面将要分析实例化Bean的过程,即从上文提到的AbstractApplicationContext类的refresh()方法中的finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法开始向底层分析。

首先进入finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法,查看其部分代码如下:

从上述finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法的部分代码看到,beanFactory.preInstantiateSingletons();这行代码是实例化Bean的。打开preInstantiateSingletons()方法的代码如下:

该方法遍历beanDefinitionNames这个ArrayList对象中的BeanName,循环调用getBean(beanName)方法。该方法实际上就是创建Bean并递归构建Bean间的依赖关系。getBean(beanName)方法最终会调用doGetBean(name, null, null, false),进入该方法查看doGetBean方法的代码,由于这个方法特别长,故下面只挑选最关键的代码作解析:

可以看到,该方法首先会获取当前Bean依赖关系mbd.getDependsOn();接着根据依赖的BeanName递归调用getBean()方法,直到调用getSingleton()方法返回依赖Bean,即当前正在创建的Bean,不断探寻依赖其的Bean,直到依赖关系最底层的Bean没有依赖的对象了,至此整个递归过程结束。getSingleton()方法的参数是createBean()方法返回值。createBean()是在AbstractAutowireCapableBeanFactory中实现的。createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法部分代码如下:

createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法的核心是doCreateBean(beanName, mbdToUse, args)这个方法,doCreateBean将会返回Bean对象的实例。查看doCreateBean的部分代码如下:

这个方法很长,这里挑选重要的两行代码进行讲解:

(1)instanceWrapper = createBeanInstance(beanName, mbd, args)用来创建实例。

(2)方法populateBean(beanName, mbd, instanceWrapper)用于填充Bean,该方法可以说就是发生依赖注入的地方。

先看方法createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[]args),其核心实现如下:

createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法会调用instantiateBean(beanName, mbd)方法,进入该方法,其部分实现如下:

instantiateBean(final String beanName, final RootBeanDefinition mbd)方法核心逻辑是beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent),发挥作用的策略对象是SimpleInstantiationStrategy,在该方法内部调用了静态方法BeanUtils.instantiateClass(constructorToUse),这个方法的部分实现如下:

该方法会判断是否是Kotlin类型。如果不是Kotlin类型,则调用Constructor的newInstance方法,也就是最终使用反射创建了该实例。

到这里,Bean的实例已经创建完成。但是Bean实例的依赖关系还没有设置,下面回到doCreateBean()方法中的populateBean(beanName, mbd, instanceWrapper)方法,该方法用于填充Bean,该方法可以说就是发生依赖注入的地方。回到AbstractAutowireCapableBeanFactory类中看一下populateBean()方法的实现。populateBean()部分代码如下:

整个方法的核心逻辑是PropertyValues pvs = (mbd.hasPropertyValues() ?mbd.getPropertyValues() : null);这一行代码,即获取该bean的所有属性,就是配置property元素,即依赖关系。最后执行applyPropertyValues(beanName, mbd, bw, pvs)方法,其实现如下:

关键代码Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);该方法是获取property对应的值。resolveValueIfNecessary(pv, originalValue)方法部分代码如下:

resolveValueIfNecessary(Object argName, @Nullable Object value)方法的核心是resolveReference(argName, ref),该方法是解决Bean依赖关系的。进入该方法的代码,查看其部分实现如下:

这段代码的核心是以下这一行:

     bean = this.beanFactory.getParentBeanFactory().getBean(refName)

这里将会发生递归调用,根据依赖的名称,从BeanFactory中递归得到依赖。到这段结束,就可以获取到依赖的Bean。回到applyPropertyValues入口处,获取到依赖的对象值后,将会调用bw.setPropertyValues(new MutablePropertyValues(deepCopy))方法,这是将依赖值注入的地方。此方法会调用AbstractPropertyAccessor类的setPropertyValues方法,查看AbstractPropertyAccessor.setPropertyValues方法的实现,其部分代码如下:

该方法会循环Bean的属性列表,循环中调用setPropertyValue(PropertyValue pv)方法,该方法是通过调用AbstractNestablePropertyAccessor.setPropertyValue (String propertyName, @Nullable Object value)方法来实现的,进入该方法的代码,其部分实现如下:

其核心是最后一行nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value))代码,进入setPropertyValue方法查看其部分实现如下:

进入processLocalProperty(tokens, pv)方法的代码,该方法非常复杂,其核心实现如下:

上述代码调用的ph.setValue(valueToApply)方法是BeanWrapperImpl.setValue(final @Nullable Object value)方法,进入这个方法的代码,查看其部分实现如下:

该方法是最后一步,这里可以看到该方法会找到属性的set方法,然后调用Method的invoke方法,完成属性注入。至此IoC容器的启动过程完毕。

下面总结一下IoC的底层原理实现:Spring IoC容器启动分为两步——创建BeanFactory和实例化Bean。Spring的Bean在内存中的状态就是BeanDefinition,在Bean的创建和依赖注入的过程中,需要根据BeanDefinition的信息来递归地完成依赖注入,从代码中可以看到,这些递归都是以getBean()为入口的,一个递归是在上下文体系中查找需要的Bean和创建Bean的依赖关系,另一个递归是在依赖注入时,通过递归调用容器的getBean方法得到当前的依赖Bean,同时也触发对依赖Bean的创建和注入。在对Bean的属性进行依赖注入时,解析过程也是一个递归过程,这样根据依赖关系,一层一层地完成Bean的创建和注入,直到与当前Bean相关的整个依赖链的注入完成。由于整个IoC容器启动过程比较复杂,本书限于篇幅,无法显示流程图。简化后的流程图如图2-10所示。更多更详细的IoC流程图请参考本书GitHub代码(https://github.com/ online-demo/spring5projectdemo),即第2章相关内容。

图2-10 IoC容器启动简易流程图 pdytulP4s9jXiaEMGBYJk38n2yufdXqiWAhULfkdWjKJGomgMFXRpkhORvFl7XjC

点击中间区域
呼出菜单
上一章
目录
下一章
×