Spring IoC实现
在之前加载注册BeanDefinition
完成后,Spring已经存在一组beanName
- >BeanDefinition
的映射了,接下来我们就可以根据名称或类型实例化并获取我们想要的Bean,而和这个实例化Bean相关的则是整个BeanFactory
体系。接下来我们从doGetBean()
这个方法开始分析起,这个方法涵盖了实例化Bean的总流程,大致步骤如下:
- 将 alias name、FactoryBean name 转换为对应的
beanName
- 尝试从缓存中获取单例Bean
- 如果缓存中不存在,则从父类容器中加载
- 合并父类的属性,获取
RootBeanDefinition
- 加载所依赖的Bean
- 根据不同的
scope
实例化Bean - 类型转换处理,如果传递的
requiredType
不为null
,则需要检测所得到的Bean的类型是否与该requiredType
一致。
首先,如果名称是以&
开头的,则去掉&
,并且根据别名获取到beanName
,然后尝试从单例缓存中获取Bean,这也正是解决循环依赖的关键,第一次获取时是没有效果的,继续往下走。此时判断是否发生原型模式的循环依赖,如果发生了,则抛出异常。接下来根据beanName
从映射中获取到BeanDefinition
(其实是RootBeanDefinition
),然后优先加载所依赖的Bean(depends-on
标签),然后根据不同的scope
分别实例化Bean,完成FactoryBean
的相关处理,最后做类型转换即可,不过目前不会去关心这个类型转换。下面开始分析比较重要的几个步骤。
Bean实例化
Bean会根据不同的scope
采取不同的实例化策略,总共有五种scope
:singleton
、prototype
、request
、session
、global session
,其实比较常用的也就singleton
和prototype
,而prototype
不需要解决循环依赖的问题,直接反射创建就好了,重点需要关注的是singleton
的实例化过程。
接下来从getSingleton()
这个方法进入Bean实例化的正戏。这个方法的开始可以看到用到了双重校验锁,因为多个线程可能在之前同时判断缓存中没有Bean,就都进入到了这里,但为了保证不重复实例化Bean(单例模式),在获取锁之后还会再判断一次是否能从缓存中获取,这里是要注意的。接下来通过ObjectFactory#getObject()
方法开始实例化,里面会调用doCreateBean()
方法,然后经历以下几个步骤:
- 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化。此时Bean已经被创建出来了,只是没有进行属性填充和初始化
- 如果为单例模式、允许循环依赖且当前单例Bean正在被创建,那么将其加入到三级缓存
singletonFactories
中 - 属性填充
- 调用初始化方法
4.1. 激活Aware
方法,对特殊的Bean处理
4.2. 调用postProcessBeforeInitialization()
4.3. 如果实现了InitializingBean
接口,调用其afterPropertiesSet()
方法;如果指定了init-method
,则调用指定的init-method
4.4. 调用postProcessAfterInitialization()
- 注册Bean的销毁方法。与
InitializingBean
和init-method
用于对象的自定义初始化工作相似,DisposableBean
和destroy-method
用于对象的自定义销毁工作。但这里并不是立刻执行,而是先注册,等到Spring容器关闭的时候才去调用,并且需要我们主动去告知Spring容器,对于BeanFactory
容器需要调用destroySingletons()
方法,对于ApplicationContext
容器需要调用registerShutdownHook()
方法。
到这里,我们就获取到一个Bean了,下面对其中的一些细节进行解释。
createBeanInstance
在这个方法中,完成了Bean的实例化(注意,此时还没填充属性等等),Spring提供了四种实例化策略:
Supplier
回调:从BeanDefinition
中获取Supplier
对象,如果不为空,则调用obtainFromSupplier()
方法完成Bean的初始化- 工厂方法初始化
- 构造函数自动注入初始化
- 默认构造函数注入
工厂方法初始化的工厂分为静态工厂和实例工厂,静态工厂的配置如下:1
<bean id="eat" class="it.spring.liao.com.EatFactory" factory-method="getInstance" />
实例工厂的配置如下:1
2<bean id="eatFactory" class="it.spring.liao.com.EatFactory "/>
<bean id="eat" factory-bean="eatFactory" factory-method="getInstance"/>
如果配置了构造函数的自动注入或者配置了构造函数参数,则调用带参的构造函数去实例化Bean。因为一个类有多个构造函数,每个构造函数都有不同的构造参数,需要根据参数个数和类型确定最精确匹配的构造函数,这部分的源码还是十分复杂的。
对于带参构造函数或默认构造函数,都会先判断是否有覆盖方法,如果有的话则使用CGLIB创建代理对象,否则通过反射来创建Bean(核心代码其实就是constructorToUse.newInstance()
)。
而对于工厂方法,其实也就是通过该Method
反射创建Bean(核心代码其实就是factoryMethod.invoke(factoryBean, args)
)。
解决循环依赖
先看一下Spring中关于这块的实现:1
2
3
4
5
6
7
8
9
10
11
12
13boolean earlySingletonExposure = (mbd.isSingleton() // 如果为单例模式
&& this.allowCircularReferences // 允许循环依赖
&& isSingletonCurrentlyInCreation(beanName)); // 当前单例 Bean 正在被创建
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 这个方法的调用发生在 createBeanInstance() 方法之后,也就是说这个 bean 其实已经创建出来了,但是没有进行属性填充和初始化,
// 但是此时已经可以根据对象引用定位到堆中该对象了,所以将该对象提前曝光出来,加入到三级缓存 singletonFactories 中
// 这里是为了后期避免循环依赖
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Spring IoC是通过三级缓存这么个机制解决循环依赖的问题的,每次在调用createBeanInstance()
方法实例化一个Bean后,就将这个Bean加入到三级缓存singletonFactories
中,而此时如果依赖的属性又依赖于自己,即发生循环依赖的话,那么就会直接从三级缓存中拿到这个Bean,并将其升级到二级缓存中去,如果后续还有循环依赖的话,直接从二级缓存就能获取到结果。需要注意的是,这时候的Bean仅仅只是实例化了出来,并没有进行属性填充等操作,只有当一切都完成后,才会将这个Bean升级到一级缓存中去。
populateBean
上面已经实例化出了Bean,并且加入到三级缓存中了,但是这个Bean还有许多事没做完,接下来第一件事就是对其进行属性填充,也就是populateBean()
这个方法,它的大体步骤如下:
- 获取Bean的属性值,也就是
PropertyValues
- 根据名称或类型解析依赖(此时并未注入到Bean中,仅仅将属性放到了`pvs中)
- 调用
applyPropertyValues()
真正注入属性:
3.1. 检测属性值列表是否已经转换过,若转换过,则直接填充属性,无需再次转换
3.2. 遍历属性值列表pvs
,解析原始值originalValue
,得到解析值resolvedValue
3.3. 对解析后的属性值resolvedValue
进行类型转换
3.4. 将类型转换后的属性值设置到PropertyValue
对象中,并将PropertyValue
对象存入deepCopy
集合中
3.5. 将deepCopy
中的属性信息注入到Bean对象中
首先讲讲根据名称或类型解析依赖。autowireByName()
方法主要完成了以下几件事:
- 获取Bean对象中的非简单属性名,即类型为对象类型的属性,
String
、Enum
、Date
、URI/URL
、Number
的继承类如Integer/Long
、byte/short/int
等基本类型、Locale
、以上所有类型的数组形式。 - 遍历那些非简单属性名,如果容器中包含该名称对应的Bean,则递归实例化该Bean(也就是调用
getBean()
方法) - 将递归获取到的Bean存入到属性值列表
PropertyValues
中 - 注册依赖(就是建立映射关系)
autowireByType()
方法比autowireByName()
方法复杂一些,因为相同类型的Bean可能有多个,它最核心的思路如下:
- 根据类型查找所有合适的候选Bean。比如说我们的成员变量是
Dao
类型的,那么此时MongoDao
和MySQLDao
这两个Bean可能都属于合适的候选项,因为它们都实现了Dao
接口。 - 如果没有找到合适的候选Bean,并且
autowire
的require
属性为true
,则直接抛出异常 - 当候选者不唯一时,则依次根据
Primary
、Priority
决定最终的候选Bean(此时拿到了autowiredBeanName
和instanceCandidate
) - 当候选者唯一时,可以直接决定候选Bean(此时拿到了
autowiredBeanName
和instanceCandidate
) - 候选Bean可能并没有实例化,也就是
instanceCandidate
仅仅为Class
类型,比如说是MySQLDao.class
,此时根据beanFactory.getBean(autowiredBeanName)
方法实例化该Bean - 返回已实例化好的Bean
接下来就和autowireByName()
方法一样了,将获取到的Bean存入到属性值列表PropertyValues
中,并且注册这个依赖关系。此时,<property>
标签表示的属性和自动注入的属性都已经解析到PropertyValues
中了,调用applyPropertyValues()
开始真正的属性注入,该方法核心步骤如下:
- 将
ref
(在之前解析标签时将其封装成了RuntimeBeanReference
)解析为具体的对象,将<list>
标签转换为List
对象,还会解析<set/>
、<map/>
、<array/>
等标签。 - 对属性值的类型进行转换,比如将
String
类型的属性值"123"
转换为Integer
类型的123
- 反射设置
PropertyValues
中的所有属性
至此,属性值已经注入到Bean中了。
initializeBean
填充完属性,接下来就是调用初始化方法,该方法的步骤如下:
- 激活
Aware
方法:Bean可以实现Aware
接口,从而对当前环境进行感知(就是实现了setXXX()
方法)。在这里,会针对BeanNameAware
、BeanClassLoaderAware
、BeanFactoryAware
三种Aware
接口进行判断,将一些值设置到当前Bean中。 - 调用
postProcessBeforeInitialization()
方法 - 调用
invokeInitMethods()
方法:
3.1. 如果Bean实现了InitializingBean
接口,那么会先调用该接口的afterPropertiesSet()
方法
3.2. 检查是否指定了init-method
,如果指定了则通过反射机制调用init-method
方法 - 调用
postProcessAfterInitialization()
方法
至此,几个核心步骤就都介绍完了。
Simple IoC实现
Simple IoC的实现大体思路上与Spring IoC一致,但是尚不支持<list/>
等一些集合标签,并且不像Spring实现了一套类型转换体系,这里直接使用了apache
的BeanUtils
完成类型转换相关的操作。除此之外,暂时还未实现initializeBean()
相关的逻辑。
快速开始
第一个测试是模拟登陆接口的场景,分为Controller
、Service
、Dao
三层,XML配置文件如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="loginController" class="cn.hecenjie.simpleioc.tests.objects.login.LoginController">
<property name="loginService" ref="loginService" />
</bean>
<bean id="loginService" class="cn.hecenjie.simpleioc.tests.objects.login.LoginServiceImpl">
<property name="userDao" ref="userDao" />
</bean>
<bean id="userDao" class="cn.hecenjie.simpleioc.tests.objects.login.UserDao"/>
</beans>
测试代码如下:1
2
3
4
5
6
7
8
9
10
public void testGetBean() {
ResourceLoader resourceLoader = new FileSystemResourceLoader();
Resource resource = resourceLoader.getResource("C:\\Users\\canjie\\Desktop\\login.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(factory);
beanDefinitionReader.loadBeanDefinitions(resource);
LoginController loginController = (LoginController) factory.getBean("loginController");
assertEquals(loginController.login("Lihua", "123456789"), true);
}
第二个测试是针对循环依赖问题的,XML配置文件如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="person" class="cn.hecenjie.simpleioc.tests.objects.persons.Person">
<property name="name" value="Lihua" />
<property name="age" value="18" />
<property name="idCard" ref="idCard" />
</bean>
<bean id="idCard" class="cn.hecenjie.simpleioc.tests.objects.persons.IdCard">
<property name="id" value="441301188875468912" />
<property name="owner" ref="person" />
</bean>
</beans>
测试代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testCyclicDependence(){
ResourceLoader resourceLoader = new FileSystemResourceLoader();
Resource resource = resourceLoader.getResource("C:\\Users\\canjie\\Desktop\\persons.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(factory);
beanDefinitionReader.loadBeanDefinitions(resource);
Person person = (Person) factory.getBean("person");
IdCard idCard = (IdCard) factory.getBean("idCard");
assertEquals(person.getName(), "Lihua");
assertEquals(person.getAge(), 18);
assertEquals(person.getIdCard(), idCard);
assertEquals(idCard.getId(), 441301188875468912L);
assertEquals(idCard.getOwner(), person);
}