【重写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 概述

设置 AbstractBeanDefinitionbeanClass 属性,这是创建对象的常规方式。如果我们希望通过工厂方法的方式创建对象,则需要指定以下两个属性。

  • 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 方法,第三步通过无参构造器创建实例是常规方式。我们来看第一步,如果 BeanDefinitionfactoryMethodName 属性不为空,则通过工厂方法注入的方式来创建对象。

//所属类[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 方法就是通过工厂方法的方式创建实例。源码中该方法的逻辑比较复杂,需要区分实例方法和静态方法,还要考虑多个重载方法的问题。我们对代码进行简化,大体上可以分为以下四步:

  1. 从 Spring 容器中找到工厂方法的声明类的实例
  2. 通过反射获取声明类的所有公开方法,找到工厂方法对应的 Method 对象
  3. 工厂方法可能存在参数,需要对方法参数进行依赖注入的处理
  4. 通过 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 组件实现的,代码看起来有点多,大体分成三步。

  1. 尝试从缓存中查找,如果不存在,则进入后续流程。
  2. 遍历构造器集合,如果声明了 @Autowired 等注解,则加入临时的 candidates 缓存中。
  3. 构建符合条件的候选项列表。
//所属类[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 注入操作

在得到候选的构造器列表之后,调用 ConstructorResolverautowireConstructor 方法。我们将整个过程分为三步,如下所示:

  1. 准备工作。对候选的构造器进行排序,排序规则是修饰符为 public 以及参数多的构造器优先级更高。
  2. 遍历所有的候选构造器,调用 createArgumentArray 方法对参数进行依赖解析,这里可能会报错,暂不处理继续处理其它的构造器。然后找出最终用于创建对象的构造器,这里简化了实现,以第一个已处理的构造器为准。
  3. 通过反射构造器的方式创建对象,并返回。

该方法的重点在于第二步,如果存在多个有参的构造器,所有构造器的参数都会被依赖解析,但最终只有一个构造器用于创建对象。一般来说,只有一个构造器,此时可以省略 @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;
    }
}

测试方法比较复杂,大体可以分为三步:

  1. 准备工作,创建 Spring 容器,注册 AutowireBeanUserService 对应的 BeanDefinition
  2. 先通过反射的方式得到测试类的所有方法,找到声明了 @MyBean 注解的方法。然后创建 BeanDefinition,但这里指定的不是 beanClass 属性,而是与工厂方法有关的属性。
  3. 确保所有的单例被创建,其中 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 (+)

注:+号表示新增、*表示修改
  • 项目地址:https://gitee.com/stimd/spring-wheel

  • 本节分支:https://gitee.com/stimd/spring-wheel/tree/chapter1-12

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


关注公众号【Java编程探微】,加群一起讨论。
在这里插入图片描述

  • 31
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值