相信大家在使用Spring进行开发的时候,肯定都使用过Spring的注解@Autowired来自动注入获取Bean。这里我们就简单的探讨一下一个小问题,一个之前被我忽略掉的问题:@Autowired属性注入,设值注入和构造注入的注入时机
这里我并不想讨论属性注入(Field DI
), 设值注入(Setter DI
)和构造注入(Constructor DI
)的好处与坏处,用那种方法更好;虽然这的确也是一个非常热门的话题,但是在我看到的资料中,对于这个问题依然争论不休,未有统一的共识,所以在这里我并不想讨论
同样,因为篇幅有限,我在这里也不讨论各种注入之间的原理,我只着重于验证如果三种注入方式都使用了,谁是最终生效者?他们之间的执行顺序是怎么样的?注入时机在什么阶段?
我们都使用过@Autowired的三种注入方式
但是你有没有想过如果我在同一个类中,对某个成员属性用三种方式分别注入,那么最后这个属性获取的Bean是哪种方式注入的呢?
又或者说,我们都知道有三种方式可以进行依赖注入,那么这三种方式在注入的时机有和不同呢?他们之间的注入顺序是怎么样的呢?
从提问阶段可以知道,我们总共需要解决两个问题:
首先验证一个猜想
- 在同一个类中,分别对同一个成员属性进行三种方式的依赖注入,最后的注入结果是哪种方式注入的?
同时要了解
- 属性注入、设值注入和构造注入的注入时机分别是什么?他们的执行顺序是怎么样的?
GitLab | 完整的代码测试用例
温馨提醒一下:测试用例中使用了Lombok插件
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是哪个一个
/**
* 测试类
*/
@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对象是哪一个实现类的实例
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创建过程中的拦截器
总之,在我们这里的目的就是,分别输出Hello1,Hello2,Hello3三个Bean的实例化前,实例化后,实例化时,填充属性前的执行时刻,记录出他们的执行顺序是怎么样?
@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)
我们可以看到HelloTest测试类的init方法和use方法输出的Hello实例都是Hello3;
所以从输出的结果中,我们可以得出结论:
- 成员属性经过属性注入,构造注入和设值注入之后,最终的结果是设值注入的Bean
属性注入,构造注入,设值注入的注入时机分析:
- 首先SpringBoot引导类启动,注入HelloTest测试类,引起HelloTest的类加载,实例化
- HelloTest作为一个Spring Bean就此开启了他的Bean生命周期
- 等轮到HelloTest的实例化阶段时,发现这里存在Hello接口的构造注入,所以实际会提前触发Hello2这个Bean的生命周期;所以我们能看到输出中,Hello2的输出是Hello实现类中最早的
- 等Hello2的Bean准备好了之后,就会被HelloTest的实例化阶段被注入到HelloTest中,这可以在HelloTest的实例化阶段和实例化后和填充属性前的输出看出来,这几个阶段的Hello属性已经是Hello3了;这里说明了构造注入是最早触发的注入, 顺序为1
- 等到HelloTest的实例化阶段结束之后,就会轮到HelloTest的填充属性阶段,此时我们看输出,就会发现Hello1这个Bean的生命周期开始被触发了,即轮到Hello1的内容被输出了,等Hello1准备完毕后,Hello3的生命周期才开始被触发,所以我们能知道属性注入在设置注入之前开始
; 这里说明了属性注入是第二被触发的注入, 顺序为2- 最后我们看到Hello3的生命周期不断输出内容,直到HelloTest的init和use方法输出的都是Hello3的内容,我们知道最后生效的是设值注入,其他的注入方式的结构都被设值注入的结果所覆盖了;
这里说明了设值注入是第三被触发的注入, 顺序为3
所以最终的结论就是:
如果三种注入方式都被使用了,最终生效的是设值注入; 他们之间的顺序是 构造注入 > 属性注入 > 设值注入
构造注入发生在Bean生命周期的实例化阶段,不是发生在填充属性阶段
属性注入和设值注入都是发生在Bean生命周期的填充属性阶段,且属性注入发生在设置注入之前