一、错误案例
@Service
@Slf4j
public class MyService {
@Value("${resend.to}")
private String to;
public MyService() {
log.info("to => {}", this.to); // 此时的to为null
}
}Spring 调用构造函数(此时to还未注入,字段是null)。
构造函数执行时,字段注入尚未完成,所以to是null。
二、可以获取到值的改造实现
方案一:使用PostConstruct
@Service
@Slf4j
public class MyService {
@Value("${resend.to}")
private String to;
public MyService() {
}
@PostConstruct
public void preProcessor() {
log.info("to => {}", this.to); // 此时可以正确获取到to的属性
}
}- Spring 调用构造函数(此时
to还未注入,字段是null)。 - Spring 通过反射给字段
to赋值(属性填充阶段)。 @PostConstruct方法执行(此时to已有值)。
tips:
@PostConstruct是在Bean初始化完成之后执行的。
方案二:构造函数注入
@Service
@Slf4j
public class MyService {
private final String to;
public MyService(@Value("${resend.to}") String to) {
this.to = to;
log.info("to => {}", this.to); // 此时可以正确获取到to的属性
}
}- Spring 解析
@Value("${resend.to}"),获取配置值。 - 调用构造函数,并将解析后的值作为参数传入(此时
to已经是有效值)。 - 构造函数执行,赋值给成员变量
this.to。
构造函数执行时,to已经被 Spring 提前解析并传入,所以不是null。
字段注入和构造器注入的区别:
| 特性 | 字段注入 (@Value在字段上) | 构造函数注入 (@Value在参数上) |
|---|---|---|
| 注入时机 | 属性填充阶段(构造函数之后) | 实例化阶段(构造函数之前) |
| 依赖解析时机 | 在属性填充时解析@Value | 在实例化时解析@Value |
| 构造函数中的值 | null(字段未注入) | 有值(参数已解析) |
是否支持final | 否(无法修饰字段) | 是(可修饰参数为final) |
| 反射操作 | 通过Field.set()注入 | 通过构造函数参数传递 |