【Spring杂烩】一起探讨@Autowired属性注入,设值注入和构造注入的注入时机

一起探讨@Autowired属性注入,设值注入和构造注入的注入时机


相信大家在使用Spring进行开发的时候,肯定都使用过Spring的注解@Autowired来自动注入获取Bean。这里我们就简单的探讨一下一个小问题,一个之前被我忽略掉的问题:@Autowired属性注入,设值注入和构造注入的注入时机


提问阶段


前提声明

这里我并不想讨论属性注入(Field DI), 设值注入(Setter DI)和构造注入(Constructor DI)的好处与坏处,用那种方法更好;虽然这的确也是一个非常热门的话题,但是在我看到的资料中,对于这个问题依然争论不休,未有统一的共识,所以在这里我并不想讨论

同样,因为篇幅有限,我在这里也不讨论各种注入之间的原理,我只着重于验证如果三种注入方式都使用了,谁是最终生效者?他们之间的执行顺序是怎么样的?注入时机在什么阶段?

问题

我们都使用过@Autowired的三种注入方式

  • 属性注入
  • 设值注入
  • 构造注入

但是你有没有想过如果我在同一个类中,对某个成员属性用三种方式分别注入,那么最后这个属性获取的Bean是哪种方式注入的呢?

又或者说,我们都知道有三种方式可以进行依赖注入,那么这三种方式在注入的时机有和不同呢?他们之间的注入顺序是怎么样的呢?


验证阶段


从提问阶段可以知道,我们总共需要解决两个问题:
首先验证一个猜想

  • 在同一个类中,分别对同一个成员属性进行三种方式的依赖注入,最后的注入结果是哪种方式注入的?

同时要了解

  • 属性注入、设值注入和构造注入的注入时机分别是什么?他们的执行顺序是怎么样的?

编码

GitLab | 完整的代码测试用例
温馨提醒一下:测试用例中使用了Lombok插件

第一步: 首先我们定义一个接口和三个实现类
【Spring杂烩】一起探讨@Autowired属性注入,设值注入和构造注入的注入时机_第1张图片

Hello接口


public interface Hello {
}

Hello1实现类

@Slf4j
@Data
@Component("hello1")
public class Hello1 implements Hello {

    String name = "hello1";

    public Hello1() {
        log.warn("hello1 constructor");
    }
}

Hello2实现类

@Slf4j
@Data
@Component("hello2")
public class hello2 implements Hello {

    String name = "hello2";

    public hello2() {
        log.warn("hello2 constructor");
    }

}

Hello3实现类

@Slf4j
@Data
@Component("hello3")
public class Hello3 implements Hello {

    String name = "hello3";

    public Hello3() {
        log.warn("hello3 constructor");
    }
}

接口是用于做静态类型去实现多态的,每个实现类都有自己的名称name,用于最后确认接口最后被注入的Bean是哪个一个

第二步: 编写测试类HelloTest
/**
 * 测试类
 */
@Slf4j
@Component
public class HelloTest {
	
	 /**
     * 属性注入
     */
    @Autowired
    @Qualifier("hello1")
    Hello hello;


    /**
     * 构造注入
     * @param hello
     */
    @Autowired
    public HelloTest(@Qualifier("hello2") Hello hello) {
        this.hello = hello;
        log.error("test-constructor: " + hello.toString());
    }

    /**
     * 设值注入
     * @param hello
     */
    @Autowired
    public void setHello(@Qualifier("hello3") Hello hello) {
        this.hello = hello;

    }

    /**
     * Bean自定义初始化阶段
     */
    @PostConstruct
    public void init() {
        log.error("test-init: " + hello.toString());
    }

    /**
     * Bean准备好后,被调用
     */
    public void use() {
        log.error("use:" + hello.toString());
    }

    @Override
    public String toString() {
        return "hello=" + hello;
    }

}

测试类中实现的就是用三种方式都依赖注入Hello接口的实例,看看最后init()use()方法中输出的hello对象是哪一个实现类的实例

  • 属性注入对应Hello1
  • 构造注入对应Hello2
  • 设值注入对应Hello3
第三步: 编写Bean的后置处理器,从Bean的生命周期角度观察

MyInstantiationAwareBeanPostProcessor类

@Component
@Slf4j
public class MyInstantiationAwareBeanPostProcessor implements  InstantiationAwareBeanPostProcessor {

	/**
     * 在Bean实例化前执行
     *
     * @param beanClass
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if ("hello1".equals(beanName)) {
            log.error("hello1 - 实例化前");
        }
        if ("hello2".equals(beanName)) {
            log.error("hello2 - 实例化前");
        }
        if ("hello3".equals(beanName)) {
            log.error("hello3 - 实例化前");
        }
        if ("helloTest".equals(beanName)) {
            log.error("helloTest - 实例化前");
        }
        return null;
    }

	/**
     * 在Bean实例化后执行
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if ("hello1".equals(beanName)) {
            log.error("hello1 - 实例化后");
        }
        if ("hello2".equals(beanName)) {
            log.error("hello2 - 实例化后");
        }
        if ("hello3".equals(beanName)) {
            log.error("hello3 - 实例化后");
        }
        if ("helloTest".equals(beanName)) {
            log.error("helloTest - 实例化后 - {}", bean);
        }
        return true;
    }

	/**
     * 在Bean生命周期中的实例化后,填充属性阶段前执行
     *
     * @param pvs
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
  	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if ("hello1".equals(beanName)) {
            log.error("hello1 - 填充属性前");
        }
        if ("hello2".equals(beanName)) {
            log.error("hello2 - 填充属性前");
        }
        if ("hello3".equals(beanName)) {
            log.error("hello3 - 填充属性前");
        }
        if ("helloTest".equals(beanName)) {
            log.error("helloTest - 填充属性前 - {}", bean);
        }
        return pvs;
    }

   

}

对Bean的生命周期不熟的可以先看这里,我这里就简单说明一下

InstantiationAwareBeanPostProcessor是一个Bean的后置处理器,我这里共实现了它的三个方法,三个方法都有自己的功能,我们可以把这个类简单理解成是Bean创建过程中的拦截器

  • postProcessBeforeInstantiation 在Bean要实例化前拦截到
  • postProcessAfterInstantiation 在Bean实例化后拦截
  • postProcessProperties则是在Bean填充属性阶段前拦截

总之,在我们这里的目的就是,分别输出Hello1,Hello2,Hello3三个Bean的实例化前,实例化后,实例化时,填充属性前的执行时刻,记录出他们的执行顺序是怎么样?

第四步: SpringBoot启动类启动

@SpringBootApplication
public class Application {

    @Autowired
    HelloTest helloTest;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @PostConstruct
    public void init() {
        helloTest.use();
    }

}

启动SpringBoot引导类,并在init方法中调用测试类的use方法


输出结果

.t.MyInstantiationAwareBeanPostProcessor : helloTest - 实例化前
.t.MyInstantiationAwareBeanPostProcessor : hello2 - 实例化前
com.snailmann.question.test.hello2       : hello2 constructor
.t.MyInstantiationAwareBeanPostProcessor : hello2 - 实例化后  .t.MyInstantiationAwareBeanPostProcessor : hello2 - 填充属性前
com.snailmann.question.test.HelloTest    : test-constructor: hello2(name=hello2)
.t.MyInstantiationAwareBeanPostProcessor : helloTest - 实例化后 - hello=hello2(name=hello2)
.t.MyInstantiationAwareBeanPostProcessor : helloTest - 填充属性前 - hello=hello2(name=hello2)
.t.MyInstantiationAwareBeanPostProcessor : hello1 - 实例化前
com.snailmann.question.test.Hello1       : hello1 constructor
.t.MyInstantiationAwareBeanPostProcessor : hello1 - 实例化后
.t.MyInstantiationAwareBeanPostProcessor : hello1 - 填充属性前
.t.MyInstantiationAwareBeanPostProcessor : hello3 - 实例化前 
com.snailmann.question.test.Hello3       : hello3 constructor.t.MyInstantiationAwareBeanPostProcessor : hello3 - 实例化后
.t.MyInstantiationAwareBeanPostProcessor : hello3 - 填充属性前
com.snailmann.question.test.HelloTest    : test-init: Hello3(name=hello3)
com.snailmann.question.test.HelloTest    : use:Hello3(name=hello3)

结论阶段


【Spring杂烩】一起探讨@Autowired属性注入,设值注入和构造注入的注入时机_第2张图片

结论一

我们可以看到HelloTest测试类的init方法和use方法输出的Hello实例都是Hello3;

所以从输出的结果中,我们可以得出结论:

  • 成员属性经过属性注入,构造注入和设值注入之后,最终的结果是设值注入的Bean

结论二

属性注入,构造注入,设值注入的注入时机分析:

  1. 首先SpringBoot引导类启动,注入HelloTest测试类,引起HelloTest的类加载,实例化
  2. HelloTest作为一个Spring Bean就此开启了他的Bean生命周期
  3. 等轮到HelloTest的实例化阶段时,发现这里存在Hello接口的构造注入,所以实际会提前触发Hello2这个Bean的生命周期;所以我们能看到输出中,Hello2的输出是Hello实现类中最早的
  4. 等Hello2的Bean准备好了之后,就会被HelloTest的实例化阶段被注入到HelloTest中,这可以在HelloTest的实例化阶段实例化后填充属性前的输出看出来,这几个阶段的Hello属性已经是Hello3了;这里说明了构造注入是最早触发的注入, 顺序为1
  5. 等到HelloTest的实例化阶段结束之后,就会轮到HelloTest的填充属性阶段,此时我们看输出,就会发现Hello1这个Bean的生命周期开始被触发了,即轮到Hello1的内容被输出了,等Hello1准备完毕后,Hello3的生命周期才开始被触发,所以我们能知道属性注入在设置注入之前开始
    这里说明了属性注入是第二被触发的注入, 顺序为2
  6. 最后我们看到Hello3的生命周期不断输出内容,直到HelloTest的init和use方法输出的都是Hello3的内容,我们知道最后生效的是设值注入,其他的注入方式的结构都被设值注入的结果所覆盖了;
    这里说明了设值注入是第三被触发的注入, 顺序为3

最终结论

所以最终的结论就是:

  • 如果三种注入方式都被使用了,最终生效的是设值注入; 他们之间的顺序是 构造注入 > 属性注入 > 设值注入

  • 构造注入发生在Bean生命周期的实例化阶段,不是发生在填充属性阶段

  • 属性注入和设值注入都是发生在Bean生命周期的填充属性阶段,且属性注入发生在设置注入之前


参考资料


  • Dependency Injection: Field Injection vs Constructor Injection? - @作者:Eric B.
  • What exactly is Field Injection and how to avoid it? - @作者:T. Jung

你可能感兴趣的:(Spring)