大家好,我在上篇博客中《关于循环引用的探讨》中,有提及Spring创建一个对象大致可以分为五个步骤,分别是“实例化”,“填充属性值”,“初始化”,“登记善后处理”,“注册单例Bean”(上述步骤暂时不考虑AOP)。PS:在Spring中创建对象和实例化对象是两个概念,实例化仅仅是创建对象过程中的一个步骤。
今天就详细分析下实例化的过程,在实例化bean阶段,已知的信息有哪些呢? 主要是bean定义对象(RootBeanDefinition) ,通过解析bean定义资源文件或注解获取的。Spring实例化的方式大致有以下几种。
一. 非默认构造函数实例化bean
上面的配置,就是一个调用非默认构造函数实例化的例子,构造函数有两个参数,类型分别是字符串和Cat,下面我们来分析下Spring实例化的每个步骤。
a. 解析实参数组,创建解析后的ConstructorArgumentValues对象
解析的核心功能是由BeanDefinitionValueResolver类实现,待解析的实现数组来自于RootBeanDefinition的成员变量ConstructorArgumentValues,此时这个对象中就包含两个待解析的实参。
zooname --- 解析前它的类型是TypedStringValue(Spring内部类型),之所以对它执行解析,是因为其中可能包含spel表达式(虽然这里没有),解析后的值还是“zooname”,类型变成了String.class。 PS:在定义Bean阶段,字符串值中可能包含两种非线性值,一种是占位符“${...}”,一种是spel表达式“#{...}”,在Spring读取bean定义信息解析成BeanDefinition对象阶段,占位符就被替换成具体值了,这个功能是通过容器后处理器PropertySourcesPlaceholderConfigurer完成的。
cat --- 解析前它的类型是RuntimeBeanReference,该类型代表对其它bean的引用,该类型有个成员变量name,代表了所引用bean的id,然后Spring会调用beanFactory.getBean("cat")方法,获取cat对象。
b. 获取候选构造函数并排序
通过RootBeanDefinition获取bean's Class,然后获取该类型所有的构造函数,由于RootBeanDefinition对象的成员变量nonPublicAccessAllowed默认值是true,所以会返回该类型的所有构造函数包括私有。然后对构造函数进行排序,public方法排在private前面,参数少的排在参数多的前面。
c. 获取匹配度最高的构造函数
遍历所有候选构造函数,执行如下操作,
c.1.根据索引或参数名称, 尝试从步骤a创建的ConstructorArgumentValues对象中获取当前候选构 造函数各参数值,如果获取不到,并且又非构造函数自动装配(Spring默认是禁止自动装配 的),则抛出异常,忽略以下步骤。
c.2. 尝试根据当前候选构造函数各形参的类型对参数执行类型转换,如果会转换不成功抛出 异常,则忽略以下步骤。
c.3. 如果转换成功,则计算实参类型和当前候选构造函数形参类型的匹配度。
最终可以获得最优的构造函数,可能会有多个,默认选用第一个。
d. 使用步骤c获取的最优构造函数实例化bean对象
同时会把最优构造函数赋值给RootBeanDefinition的成员变量Object resolvedConstructorOrFactoryMethod,避免下次重复解析。
二. 默认构造函数实例化bean
这种情况就简单多了,直接使用默认构造函数实例化bean对象。
三. 静态工厂方法实例化对象
public class UserServiceFactory
{
/**
* 演示静态工厂方法创建对象
*/
public static UserService createUserService_static(String name)
{
System.out.println(name);
return new UserService();
}
}
上面就是一个使用静态工厂方法实例化bean的例子,这段配置的意思是使用UserServiceFactory类的静态方法createUserService_static(String name)创建UserService对象。Spring是如何实现该功能的呢,我们来分析下。
a. 获取工厂Class
对于静态工厂方法工厂类型已经在Bean配置中定义了,这这个列子中就是“org.UserServiceFactory”可以通过RootBeanDefinition#getBeanClass()方法获取。
b. 解析实参数组,创建解析后的ConstructorArgumentValues对象
可参考非默认构造函数实例化bean该步骤的处理逻辑,基本上是一模一样的。
c. 获取候选工厂方法对象并排序
首先根据工厂Class获取该类定义的所有方法对象,然后遍历方法对象,对满足以下条件的方法认为是候选方法对象。
c.1. 方法是静态的。
c.2. 方法的名称同配置一致,这里应该是“createUserService_static”。
d. 获取匹配度最高的工厂方法对象
可以参考非默认构造函数实例化bean的处理逻辑,核心逻辑是判断形参类型和实参类型的匹配度,获取最优的方法对象。
e. 调用最优工厂方法对象创建对象。
如果有多个最优工厂方法对象,默认使用第一个。
三. 实例工厂方法
public class UserServiceFactory
{
/**
* 演示实例工厂方法创建对象
*/
public UserService createUserService(String name)
{
System.out.println(name);
return new UserService();
}
}
上面是一个实例工厂方法的例子,跟静态工厂方法的区别是工厂本身是一个独立的bean对象,并且工厂方法“CreateUserService”是UserServiceFactory类的实例方法而非静态方法,Spring调用实例工厂方法创建对象跟静态工厂方法创建对象的过程区别在于步骤a,其余步骤的处理逻辑两者是相同的。
实例工厂方法的步骤a
a. 获取工厂Class
a.1. 首先通过beanFactory.getBean("usFactory")创建工厂对象。
a.2. 然后调用factoryBean.getClass()方法获取工厂Class。
四. 通过工厂Bean创建对象
下面是一个工厂bean的例子。
public class GetFieldFactoryBean implements FactoryBean
工厂bean是指该bean实现了FactoryBean接口,这个bean自身的实例化可以参考“非默认构造函数实例化bean”或“默认构造函数实例化bean”。但是当用户通过ctx.getBean("ffb")获取对象时,返回不是工厂bean自身,而是会调用工厂bean的getObject()方法创建对象并返回。如果用户希望获取工厂bean对象自身,可以通过ctx.getBean("&ffb")实现。
如果是单例工厂bean, Spring启动时虽然会创建单例工厂bean对象自身,但是不会调用工厂bean的getObject()方法创建对象,除非该工厂bean实现了SmartFactoryBean接口并且对接口方法isEagerInit()的实现是返回true,不过Spring提议用户如果开发工厂Bean最好还是直接实现FactoryBean接口,SmartFactoryBean接口是Spring内部使用的。
五. lookup和replace
对于lookup和replace大家了解下就信,不太常用,从实例化的角度看它们有一个共同的特点,就是Spring会使用cglib技术创建bean的派生类,从而实现对原方法的拦截,织入另外的代码,来实现特定的功能。
下面是一个lookup的使用示例,
public abstract class Company
{
/**
* 定义派生类需要实现的抽象方法
*/
public abstract Person getPerson();
}
基于以上的配置,当Spring去创建company对象时,其实是用cglib技术创建了company的派生类,并且该派生类实现了Company类中定义的抽象方法getPerson(),派生类对于抽象方法getPerson()的实现逻辑大致如下。
public Person getPerson()
{
beanFactory.getBean("person");
}
Spring之所以设计lookup,是为了解决Bean作用域不同步的问题,在本例中通过单例的company对象的方法可以获取到生命周期是属性的person对象,每次调用getPerson()方法返回的都是新的Person对象。