Spring IoC实现

在之前加载注册BeanDefinition完成后,Spring已经存在一组beanName - >BeanDefinition的映射了,接下来我们就可以根据名称或类型实例化并获取我们想要的Bean,而和这个实例化Bean相关的则是整个BeanFactory体系。接下来我们从doGetBean()这个方法开始分析起,这个方法涵盖了实例化Bean的总流程,大致步骤如下:

  1. 将 alias name、FactoryBean name 转换为对应的beanName
  2. 尝试从缓存中获取单例Bean
  3. 如果缓存中不存在,则从父类容器中加载
  4. 合并父类的属性,获取RootBeanDefinition
  5. 加载所依赖的Bean
  6. 根据不同的scope实例化Bean
  7. 类型转换处理,如果传递的requiredType不为null,则需要检测所得到的Bean的类型是否与该requiredType一致。

首先,如果名称是以&开头的,则去掉&,并且根据别名获取到beanName,然后尝试从单例缓存中获取Bean,这也正是解决循环依赖的关键,第一次获取时是没有效果的,继续往下走。此时判断是否发生原型模式的循环依赖,如果发生了,则抛出异常。接下来根据beanName从映射中获取到BeanDefinition(其实是RootBeanDefinition),然后优先加载所依赖的Bean(depends-on标签),然后根据不同的scope分别实例化Bean,完成FactoryBean的相关处理,最后做类型转换即可,不过目前不会去关心这个类型转换。下面开始分析比较重要的几个步骤。

Bean实例化

Bean会根据不同的scope采取不同的实例化策略,总共有五种scopesingletonprototyperequestsessionglobal session,其实比较常用的也就singletonprototype,而prototype不需要解决循环依赖的问题,直接反射创建就好了,重点需要关注的是singleton的实例化过程。

接下来从getSingleton()这个方法进入Bean实例化的正戏。这个方法的开始可以看到用到了双重校验锁,因为多个线程可能在之前同时判断缓存中没有Bean,就都进入到了这里,但为了保证不重复实例化Bean(单例模式),在获取锁之后还会再判断一次是否能从缓存中获取,这里是要注意的。接下来通过ObjectFactory#getObject()方法开始实例化,里面会调用doCreateBean()方法,然后经历以下几个步骤:

  1. 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化。此时Bean已经被创建出来了,只是没有进行属性填充和初始化
  2. 如果为单例模式、允许循环依赖且当前单例Bean正在被创建,那么将其加入到三级缓存singletonFactories
  3. 属性填充
  4. 调用初始化方法
    4.1. 激活Aware方法,对特殊的Bean处理
    4.2. 调用postProcessBeforeInitialization()
    4.3. 如果实现了InitializingBean接口,调用其afterPropertiesSet()方法;如果指定了init-method,则调用指定的init-method
    4.4. 调用postProcessAfterInitialization()
  5. 注册Bean的销毁方法。与InitializingBeaninit-method用于对象的自定义初始化工作相似,DisposableBeandestroy-method用于对象的自定义销毁工作。但这里并不是立刻执行,而是先注册,等到Spring容器关闭的时候才去调用,并且需要我们主动去告知Spring容器,对于BeanFactory容器需要调用destroySingletons()方法,对于ApplicationContext容器需要调用registerShutdownHook()方法。

到这里,我们就获取到一个Bean了,下面对其中的一些细节进行解释。

createBeanInstance

在这个方法中,完成了Bean的实例化(注意,此时还没填充属性等等),Spring提供了四种实例化策略:

  1. Supplier回调:从BeanDefinition中获取Supplier对象,如果不为空,则调用obtainFromSupplier()方法完成Bean的初始化
  2. 工厂方法初始化
  3. 构造函数自动注入初始化
  4. 默认构造函数注入

工厂方法初始化的工厂分为静态工厂和实例工厂,静态工厂的配置如下:

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
13
boolean 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()这个方法,它的大体步骤如下:

  1. 获取Bean的属性值,也就是PropertyValues
  2. 根据名称或类型解析依赖(此时并未注入到Bean中,仅仅将属性放到了`pvs中)
  3. 调用applyPropertyValues()真正注入属性:
    3.1. 检测属性值列表是否已经转换过,若转换过,则直接填充属性,无需再次转换
    3.2. 遍历属性值列表pvs,解析原始值originalValue,得到解析值resolvedValue
    3.3. 对解析后的属性值resolvedValue进行类型转换
    3.4. 将类型转换后的属性值设置到PropertyValue对象中,并将PropertyValue对象存入deepCopy集合中
    3.5. 将deepCopy中的属性信息注入到Bean对象中

首先讲讲根据名称或类型解析依赖。autowireByName()方法主要完成了以下几件事:

  1. 获取Bean对象中的非简单属性名,即类型为对象类型的属性,StringEnumDateURI/URLNumber的继承类如Integer/Longbyte/short/int等基本类型、Locale、以上所有类型的数组形式。
  2. 遍历那些非简单属性名,如果容器中包含该名称对应的Bean,则递归实例化该Bean(也就是调用getBean()方法)
  3. 将递归获取到的Bean存入到属性值列表PropertyValues
  4. 注册依赖(就是建立映射关系)

autowireByType()方法比autowireByName()方法复杂一些,因为相同类型的Bean可能有多个,它最核心的思路如下:

  1. 根据类型查找所有合适的候选Bean。比如说我们的成员变量是Dao类型的,那么此时MongoDaoMySQLDao这两个Bean可能都属于合适的候选项,因为它们都实现了Dao接口。
  2. 如果没有找到合适的候选Bean,并且autowirerequire属性为true,则直接抛出异常
  3. 当候选者不唯一时,则依次根据PrimaryPriority决定最终的候选Bean(此时拿到了autowiredBeanNameinstanceCandidate
  4. 当候选者唯一时,可以直接决定候选Bean(此时拿到了autowiredBeanNameinstanceCandidate
  5. 候选Bean可能并没有实例化,也就是instanceCandidate仅仅为Class类型,比如说是MySQLDao.class,此时根据beanFactory.getBean(autowiredBeanName)方法实例化该Bean
  6. 返回已实例化好的Bean

接下来就和autowireByName()方法一样了,将获取到的Bean存入到属性值列表PropertyValues中,并且注册这个依赖关系。此时,<property>标签表示的属性和自动注入的属性都已经解析到PropertyValues中了,调用applyPropertyValues()开始真正的属性注入,该方法核心步骤如下:

  1. ref(在之前解析标签时将其封装成了RuntimeBeanReference)解析为具体的对象,将<list>标签转换为List对象,还会解析<set/><map/><array/>等标签。
  2. 对属性值的类型进行转换,比如将String类型的属性值"123"转换为Integer类型的123
  3. 反射设置PropertyValues中的所有属性

至此,属性值已经注入到Bean中了。

initializeBean

填充完属性,接下来就是调用初始化方法,该方法的步骤如下:

  1. 激活Aware方法:Bean可以实现Aware接口,从而对当前环境进行感知(就是实现了setXXX()方法)。在这里,会针对BeanNameAwareBeanClassLoaderAwareBeanFactoryAware三种Aware接口进行判断,将一些值设置到当前Bean中。
  2. 调用postProcessBeforeInitialization()方法
  3. 调用invokeInitMethods()方法:
    3.1. 如果Bean实现了InitializingBean接口,那么会先调用该接口的afterPropertiesSet()方法
    3.2. 检查是否指定了init-method,如果指定了则通过反射机制调用init-method方法
  4. 调用postProcessAfterInitialization()方法

至此,几个核心步骤就都介绍完了。

Simple IoC实现

Simple IoC的实现大体思路上与Spring IoC一致,但是尚不支持<list/>等一些集合标签,并且不像Spring实现了一套类型转换体系,这里直接使用了apacheBeanUtils完成类型转换相关的操作。除此之外,暂时还未实现initializeBean()相关的逻辑。

快速开始

第一个测试是模拟登陆接口的场景,分为ControllerServiceDao三层,XML配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>

<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
@Test
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
<?xml version="1.0" encoding="UTF-8"?>

<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
@Test
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);
}