之前也是看过、用过 @Value 来绑定配置文件中定义的属性值的。通过百度完善了部分拼图,除了 @Value 定义属性外,还需要对使用的类添加 @Component 以便类能够被扫描到。

测试用例来自 netty writing-a-discard-server demo

public class DiscardServer {
    
    private int port;
    
    public DiscardServer(int port) {
        this.port = port;
    }

...

在 properties 文件里增加 netty.port=8888,并对类增加相关注解,但在类的构造函数中确是获取不到 @Value 注解的值的:

@Slf4j
@Component
public class DiscardServer {
    
    @Value("${netty.port:8088}")
    private int port;
    
    public DiscardServer() {
        log.info("netty port:" + port);
    }

...

之后因为构造函数获取不到 port 而去怀疑参数定义和方法是否正确,无果。

尝试增加一个配置类,使用配置类来独立设置属性:

@Slf4j
@Component
public class NettyConfig {

    @Value("${netty.port:8088}")
    private int port;

    public NettyConfig() {
        log.info("netty config port:" + port);
    }

    public int getPort(){
        return port;
    }
}

然后在 DiscardServer 构造函数中调用(去除 DiscardServer 中的 @Value 注解):this->port = new NettyConfig()->getPort();,结果日志输出依然是 0。

继续百度,找到了一篇针对 @Value 获取值为空的原因分析。对照发现问题:

情况五:类被new新建了实例,而没有使用@Autowired

这是我不知道的,使用 @Autowired 代替 new。

    @Autowired
    private NettyConfig nettyConfig;
...
    public DiscardServer() {
        // 方法调用 'getPort' 可能生成 'NullPointerException'
        this->port = nettyConfig.getPort();
    }
...

有一个警告:方法调用 'getPort' 可能生成 'NullPointerException',忽略去调试结果真的就报这个错了。使用 IDE 的查找原因,结果是找不到。这似乎又是一团乱麻。

回过头来如果直接使用 nettyConfig.getPort(),可以,这意味着在不适用 NettyConfig 类之前,在非构造函数中直接输出 port 也是可以的。

总结:

  • @Value 注解值在构造函数中是获取不到,但可以在其他实例方法中直接使用
  • 当包含 @Value 注解的类实例化时,需要使用 @Resource 或者 @Autowired 自动装配到调用类,而非 new 实例化
  • @Autowired@Resource 都可以自动装配类,但 @Autowired 在使用 Spring 框架才有。