一、错误案例

java
@Service
@Slf4j
public class MyService {
	@Value("${resend.to}")
	private String to;

	public MyService() {
		log.info("to => {}", this.to); // 此时的to为null
	}
}

Spring 调用构造函数(此时to还未注入,字段是null)。

构造函数执行时,字段注入尚未完成,所以tonull


二、可以获取到值的改造实现

方案一:使用PostConstruct

java
@Service
@Slf4j
public class MyService {
    @Value("${resend.to}")
    private String to;

    public MyService() {

    }

    @PostConstruct
    public void preProcessor() {
        log.info("to => {}", this.to); // 此时可以正确获取到to的属性
    }
}
  1. Spring 调用构造函数(此时to还未注入,字段是null)。
  2. Spring 通过反射给字段to赋值(属性填充阶段)。
  3. @PostConstruct方法执行(此时to已有值)。

tips:

@PostConstruct是在Bean初始化完成之后执行的。


方案二:构造函数注入

java
@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的属性
    }
}
  1. Spring 解析@Value("${resend.to}"),获取配置值。
  2. 调用构造函数,并将解析后的值作为参数传入(此时to已经是有效值)。
  3. 构造函数执行,赋值给成员变量this.to

构造函数执行时,to已经被 Spring 提前解析并传入,所以不是null

字段注入和构造器注入的区别:

特性字段注入 (@Value在字段上)构造函数注入 (@Value在参数上)
注入时机属性填充阶段(构造函数之后)实例化阶段(构造函数之前)
依赖解析时机在属性填充时解析@Value在实例化时解析@Value
构造函数中的值null(字段未注入)有值(参数已解析)
是否支持final否(无法修饰字段)是(可修饰参数为final
反射操作通过Field.set()注入通过构造函数参数传递