【重写SpringFramework】第一章beans模块:工厂方法和构造器注入(chapter 1-12)
1. 前言
前边提到,自动装配可以通过字段注入或 setter 方法注入的方式实现。实际上,Spring 还提供了两种自动装配的方式,即工厂方法注入和构造器注入。这两种方式主要为配置类服务,所谓配置类是指声明了 @Configuration
等注解的类,专门用于完成各项配置工作,有关配置类的内容将在第三章 context 模块详细讲解。
工厂方法是指声明了 @Bean
注解的方法,有两个特点。一是方法参数会执行依赖解析的流程,二是返回一个对象并注册到 Spring 容器中。示例代码如下,Spring 将对参数 bar
进行依赖解析,然后创建 Foo
对象并注册为单例。
//示例代码:工厂方法注入
@Configuration
public class AutowireConfig() {
@Bean
public Foo foo(Bar bar){
return new Foo(bar);
}
}
构造器注入则是指在构造器声明 @Autowired
注解,同样地,Spring 将对构造器参数进行依赖解析。示例代码如下,这里使用到了构造器注入的一种特例,即如果只有一个有参的构造器,可以不声明注解。
//示例代码:构造器注入
@Configuration
public class AutowireConfig() {
private Foo foo;
public AutowireConfig(Foo foo){
this.foo = foo;
}
}
2. 自动装配流程
我们将自动装配的实现方式分为两组,其中字段注入与 setter 方法注入的作用是为字段赋值,工厂方法注入和构造器注入的作用是创建对象,只是在创建对象的过程中完成了对象的依赖解析。除了用途不同,还可以从执行流程来分析,我们结合流程图来看。
- 工厂方法注入和构造器注入发生在创建实例的阶段,由
ConstructorResolver
组件来处理。 - 字段注入和 setter 方法注入发生在填充对象的阶段,由
AutowiredAnnotationBeanPostProcessor
等组件来处理。
3. 工厂方法注入
3.1 概述
设置 AbstractBeanDefinition
的 beanClass
属性,这是创建对象的常规方式。如果我们希望通过工厂方法注入的方式创建对象,则需要指定以下两个属性。
factoryBeanName
表示工厂方法所在的类的名称,通过 beanName 获取配置类的实例,作为反射的目标对象。factoryMethodName
表示工厂方法的名称,然后找到方法实例,作为反射的调用方法。
public abstract class AbstractBeanDefinition extends AttributeAccessorSupport implements BeanDefinition {
private String factoryBeanName;
private String factoryMethodName;
}
RootBeanDefinition
也有相关的属性,简单介绍如下:
-
factoryMethodReturnType
:表示工厂方法的返回类型,也就是待创建的对象类型。有时需要判断对象的类型,对于工厂方法来说就是返回值的类型。 -
resolvedConstructorOrFactoryMethod
:表示一个方法或构造器,通过反射的方式调用。 -
isFactoryMethodUnique
:判断工厂方法是否唯一,可能存在多个重载方法或构造器。
public class RootBeanDefinition extends AbstractBeanDefinition {
volatile ResolvableType factoryMethodReturnType;
Object resolvedConstructorOrFactoryMethod;
boolean isFactoryMethodUnique = false;
}
3.2 代码实现
回到 AbstractAutowireCapableBeanFactory
类的 createBeanInstance
方法,第三步通过无参构造器创建实例是常规方式。我们来看第一步,如果 BeanDefinition
的 factoryMethodName
属性不为空,则通过工厂方法注入的方式来创建对象。
//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//创建实例
private BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd) {
//1. 工厂方法
if(mbd.getFactoryMethodName() != null){
return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd);
}
//2. 构造器注入(TODO)
//3. 无参构造器(略)
}
从方法名就能看出,instantiateUsingFactoryMethod
方法就是通过工厂方法的方式创建实例。源码中该方法的逻辑比较复杂,需要区分实例方法和静态方法,还要考虑多个重载方法的问题。我们对代码进行简化,大体上可以分为以下四步:
- 从 Spring 容器中找到工厂方法的声明类的实例
- 通过反射获取声明类的所有公开方法,找到工厂方法对应的
Method
对象 - 工厂方法可能存在参数,需要对方法参数进行依赖注入的处理
- 通过
InstantiationStrategy
完成实例化工作,实际上是通过反射的方式调用声明类的工厂方法
//所属类[cn.stimd.spring.beans.factory.support.ConstructorResolver]
//通过工厂方法创建对象
public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd) {
BeanWrapperImpl bw = new BeanWrapperImpl();
String factoryBeanName = mbd.getFactoryBeanName();
Object factoryBean = null;
Class<?> factoryClass = null;
//1. 获取工厂方法声明类的实例
if(factoryBeanName != null) {
factoryBean = this.beanFactory.getBean(factoryBeanName);
factoryClass = factoryBean.getClass();
}
//2. 获取工厂方法(不考虑多个重载方法的情况,简单认为只有一个工厂方法)
Method factoryMethodToUse = (Method)mbd.resolvedConstructorOrFactoryMethod;
if(factoryMethodToUse == null){
Method[] rawCandidates = ReflectionUtils.getAllDeclaredMethods(factoryClass);
for (Method candidate : rawCandidates) {
if (mbd.isFactoryMethod(candidate)) {
factoryMethodToUse = candidate;
mbd.resolvedConstructorOrFactoryMethod = candidate;
break;
}
}
}
//3. 如果工厂方法存在参数,则需要解析依赖项
Object[] argsToUse = null;
Class<?>[] paramTypes = factoryMethodToUse.getParameterTypes();
if(paramTypes.length > 0){
argsToUse = createArgumentArray(beanName, paramTypes, factoryMethodToUse);
}
//4. 实例化对象
Object instance = this.beanFactory.getInstantiationStrategy().instantiate(beanName, factoryBean, factoryMethodToUse, argsToUse);
bw.setBeanInstance(instance);
return bw;
}
第三步比较重要,单独拿出来讲一讲。之前我们讲过,工厂方法注入的特点是,即使方法参数没有声明 @Autowired
等注解,Spring 容器也将参数看做依赖注入的目标。首先获取方法的参数类型的数组,然后调用 createArgumentArray
方法。遍历参数类型数组,构建 DependencyDescriptor
对象,最关键的一步是把依赖解析的工作委托给 Spring 容器完成,从而得到每个参数对应的依赖对象。
//所属类[cn.stimd.spring.beans.factory.support.ConstructorResolver]
//对形参进行依赖解析,获取实参数组
private Object[] createArgumentArray(String beanName, Class<?>[] paramTypes, Object methodOrCtor) {
Object[] arguments = new Object[paramTypes.length];
//遍历方法参数,依次进行解析
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
DependencyDescriptor descriptor = new DependencyDescriptor(methodParam, true);
//解析参数的依赖项
Object autowiredArgument = this.beanFactory.resolveDependency(descriptor, beanName);
arguments[paramIndex] = autowiredArgument;
}
return arguments;
}
4. 构造器注入
4.1 主流程
构造器注入也发生在实例化阶段,我们来看 createBeanInstance
方法的第二步。先尝试获取候选的构造器集合,如果集合不为空,则由 ConstructorResolver
组件对构造器进行解析,最终创建一个对象并返回。
//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//创建实例
private BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd) {
//1. 工厂方法(略)
//2. 构造器注入
Class<?> beanClass = resolveBeanClass(mbd, beanName);
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null) {
return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors);
}
//3. 无参构造器(略)
}
4.2 寻找候选的构造器
determineCandidateConstructors
方法是由 AutowiredAnnotationBeanPostProcessor
组件实现的,代码看起来有点多,大体分成三步。
- 尝试从缓存中查找,如果不存在,则进入后续流程。
- 遍历构造器集合,如果声明了
@Autowired
等注解,则加入临时的candidates
缓存中。 - 构建符合条件的候选项列表。
//所属类[cn.stimd.spring.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]
//寻找候选的构造器
@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) {
//1. 尝试从缓存中查找
Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
synchronized (this.candidateConstructorsCache) {
candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
Constructor<?>[] rawCandidates = beanClass.getDeclaredConstructors();
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
Constructor<?> requiredConstructor = null;
Constructor<?> defaultConstructor = null;
//2. 遍历构造器集合
for (Constructor<?> candidate : rawCandidates) {
//2.1 获取构造器的注解信息
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
if (ann == null) {
//Bean可能是Cglib方式生成的,尝试获取原始类的构造器注解
Class<?> userClass = ClassUtils.getUserClass(beanClass);
if (userClass != beanClass) {
try {
Constructor<?> superCtor = userClass.getDeclaredConstructor(candidate.getParameterTypes());
ann = findAutowiredAnnotation(superCtor);
}
catch (NoSuchMethodException ex) {
// ignore
}
}
}
//2.2 如果存在注解,则将构造器加入候选列表
if (ann != null) {
//确保标记为自动装配且必须的构造器只有一个
if(requiredConstructor != null){
throw new BeansException("创建Bean[" + beanName + "]失败: 构造器[" + candidate +
"]不能被标记为自动注入,已经存在被标记为自动注入的构造器: " + requiredConstructor);
}
//判断required是否为true
boolean required = determineRequiredStatus(ann);
if(required){
requiredConstructor = candidate;
}
candidates.add(candidate);
}
//2.3 如果存在无参构造器,先缓存起来,作为可选构造器的补充
else if (candidate.getParameterTypes().length == 0) {
defaultConstructor = candidate;
}
}
//3. 过滤符合条件的构造器
if (!candidates.isEmpty()) {
//如果不存在required属性为true的构造器,且无参构造器存在,则加入到临时集合
if (requiredConstructor == null && defaultConstructor != null) {
candidates.add(defaultConstructor);
}
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
}
//只有一个构造器,且有参数
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterTypes().length > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
}
//其余情况不予考虑,如果进入这个分支,最终返回null
else {
candidateConstructors = new Constructor<?>[0];
}
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
}
}
}
return (candidateConstructors.length > 0 ? candidateConstructors : null);
}
我们重点关注哪些构造器会被认为符合条件,需要符合以下几个原则:
- 如果构造器声明了
@Autowired
等注解,且required
属性为 true,那么只能存在一个构造器,否则报错。 - 允许存在多个构造器,但必须声明
@Autowired
注解且required
属性为 false。如果存在无参构造器,也加入候选列表。 - 如果只有一个构造器且存在参数,但没有声明注解,也认为是候选项。
4.3 注入操作
在得到候选的构造器列表之后,调用 ConstructorResolver
的 autowireConstructor
方法。我们将整个过程分为三步,如下所示:
- 准备工作。对候选的构造器进行排序,排序规则是修饰符为 public 以及参数多的构造器优先级更高。
- 遍历所有的候选构造器,调用
createArgumentArray
方法对参数进行依赖解析,这里可能会报错,暂不处理继续处理其它的构造器。然后找出最终用于创建对象的构造器,这里简化了实现,以第一个已处理的构造器为准。 - 通过反射构造器的方式创建对象,并返回。
该方法的重点在于第二步,如果存在多个有参的构造器,所有构造器的参数都会被依赖解析,但最终只有一个构造器用于创建对象。一般来说,只有一个构造器,此时可以省略 @Autowired
等注解,配置类就是如此实现的。
//所属类[cn.stimd.spring.beans.factory.support.ConstructorResolver]
//通过构造器创建对象,并完成参数的自动装配
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, Constructor<?>[] chosenCtors) {
//1. 准备工作
BeanWrapperImpl bw = new BeanWrapperImpl();
Constructor<?>[] candidates = chosenCtors;
if (candidates == null) {
candidates = mbd.getBeanClass().getDeclaredConstructors();
}
//对候选的构造器进行排序,优先选择公共的以及参数多的构造器
AutowireUtils.sortConstructors(candidates);
Constructor<?> constructorToUse = null; //用于创建对象的构造器
Object[] argsToUse = null; //用于创建对象的构造器参数
Exception exception = null;
//2. 对构造器进行解析,如果存在多个构造器,则找出最适合的一个
for (Constructor<?> candidate : candidates) {
Class<?>[] paramTypes = candidate.getParameterTypes();
Object[] args;
try {
args = createArgumentArray(beanName, paramTypes, candidate);
}catch (Exception e){
//解析依赖时,如果Spring中不存在对应的Bean,将会抛出异常,这里不处理,继续遍历其他候选项
exception = e;
continue;
}
//找出最适合的构造器(简化实现,以第一个构造器作为最终构造器)
if(constructorToUse == null) {
constructorToUse = candidate;
argsToUse = args; //源码无,确保参数与构造器对应
}
}
if (constructorToUse == null) {
throw new BeansException("解析Bean[" + beanName + "]的构造器失败", exception);
}
//3. 反射构造器创建实例
Object instance = this.beanFactory.getInstantiationStrategy().instantiate(mbd, beanName, constructorToUse, argsToUse);
bw.setBeanInstance(instance);
return bw;
}
5. 测试
5.1 工厂方法注入
首先定义一个 @MyBean
注解,代码略(为了与第三章的 @Bean
注解区分开,类名略有不同)。然后在测试类中,userController
方法声明了 @MyBean
注解,这里模拟的是配置类中的工厂方法。在实际使用中,@Bean
注解声明的方法会将返回的 UserController
对象注册到 Spring 容器中。
//测试类
public class AutowireBean {
@MyBean
public UserController userController(UserService userService) {
UserController userController = new UserController();
userController.setUserService(userService);
return userController;
}
}
测试方法比较复杂,大体可以分为三步:
- 准备工作,创建 Spring 容器,注册
AutowireBean
和UserService
对应的BeanDefinition
。 - 先通过反射的方式得到测试类的所有方法,找到声明了
@MyBean
注解的方法。然后创建BeanDefinition
,但这里指定的不是beanClass
属性,而是与工厂方法有关的属性。 - 确保所有的单例被创建,其中
UerController
是通过工厂方法的方式创建的,完成了对UserService
实例的依赖解析。
//测试方法
@Test
public void testFactoryMethod(){
//1. 准备工作
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("autowireBean", new RootBeanDefinition(AutowireBean.class));
factory.registerBeanDefinition("userService", new RootBeanDefinition(UserService.class));
//2. 准备 UserController 的BeanDefinition
//寻找声明了@MyBean注解的方法
Method factoryMethod = null;
Method[] methods = AutowireBean.class.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MyBean.class)) {
factoryMethod = method;
break;
}
}
if(factoryMethod != null){
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setFactoryBeanName("autowireBean");
beanDefinition.setUniqueFactoryMethodName(factoryMethod.getName());
factory.registerBeanDefinition("userController", beanDefinition);
}
//3. 执行获取Bean的流程
factory.preInstantiateSingletons();
UserController userController = factory.getBean("userController", UserController.class);
System.out.println("工厂方法注入测试: " + userController.getUser());
}
从测试结果可以看到,UserService
作为依赖项得到了解析,UserController
也成功地注册到了 Spring 容器中,符合预期。
工厂方法注入测试: User{name='Tom', age=20}
5.2 构造器注入
在测试类 AutowireBean
中定义一个 User
字段,并在构造器上声明 @Autowired
注解。当然也可以不声明注解,效果是一样的,读者可自行测试。
//测试类
public class AutowireBean {
User user;
@Autowired
public AutowireBean(User user) {
this.user = user;
}
}
测试方法较为简单,需要注册 AutowiredAnnotationBeanPostProcessor
组件来寻找候选的构造器集合。
//测试方法
@Test
public void testAutowiredConstructor(){
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
processor.setBeanFactory(factory);
factory.addBeanPostProcessor(processor);
factory.registerBeanDefinition("autowireBean", new RootBeanDefinition(AutowireBean.class));
factory.registerSingleton("user", new User("Tom", 20));
AutowireBean factoryConfig = factory.getBean("autowireBean", AutowireBean.class);
System.out.println("构造器注入测试:" + factoryConfig.user);
}
从测试结果可以看到,AutowireBean
构造器的参数被解析,符合预期。
构造器注入测试:User{name='Tom', age=20}
6. 总结
本节我们介绍了为什么要将四种注入方式分为两组,原因有二,一是用途不同,二是执行的流程不同。具体来说,字段注入和 setter 方法注入是为了给字段赋值,在填充对象的流程中调用。工厂方法注入和构造器注入的目的是创建对象,在创建实例的流程中调用。
在实际应用中,工厂方法注入和构造器注入都是为配置类提供服务的。在源码中,这两种实现方式较为复杂,我们进行了一定的简化。
- 工厂方法分为实例方法和静态方法两种,且一个类可能存在多个同名的重载方法。我们只实现了实例工厂方法,且不考虑同时存在多个重载方法的情况。
- 构造器注入则分为多种情况,如果存在多个构造器,需要根据算法选择一个最合适的构造器来创建对象。我们直接选择第一个构造器,大多数情况下也只有一个构造器,因此影响不大。
7. 项目信息
新增修改一览,新增(4),修改(7)。
beans
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.spring.beans
│ └─ factory
│ ├─ annotation
│ │ ├─ Autowired.java (*)
│ │ └─ AutowiredAnnotationBeanPostProcessor.java (*)
│ ├─ config
│ │ └─ InstantiationAwareBeanPostProcessor.java (*)
│ └─ support
│ ├─ AbstractAutowireCapableBeanFactory.java (*)
│ ├─ AutowireUtils.java (+)
│ ├─ ConstructorResolver.java (+)
│ ├─ InstantiationStrategy.java (*)
│ └─ SimpleInstantiationStrategy.java (*)
└─ test
└─ java
└─ beans
└─ autowire
├─ AutowireBean.java (+)
├─ AutowireTest.java (*)
└─ MyBean.java (+)
注:+号表示新增、*表示修改
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请关注、点赞、收藏。
转载自:https://juejin.cn/post/7380694342746243110