Spring Environment 注入引起NPE问题排查

发布时间:2023年12月22日

背景

写业务代码遇到使用 Spring Environment 注入为 null 的情况,示例代码有以下两种写法,Environment 实例都无法注入成功,均为 null。

  • 写法一:直接在业务 Bean 中注入 Environment 类
public class IPayInstitutionManager {
  
  @Autowire
  private Environment environment;
  
  public IPayInstitutionManager() {
    log.info("IPayInstitutionManager start to init...");
    log.info("IPayInstitutionManager environment:{}", environment);
    log.info("testPrivate:{}", environment.getProperty("test.key"));
    init();
  }
}
  • 写法二:使用 EnvironmentAware
public class IPayInstitutionManager implements EnvironmentAware {
  
  private Environment environment;
  
  @Override
  public void setEnvironment(Environment environment) {
    this.environment = environment;
    log.info("IPayInstitutionManager#setEnvironment environment:{}", environment);
    log.info("IPayInstitutionManager#setEnvironment value:{}", environment.getProperty("test.key"));
  }
  
  public IPayInstitutionManager() {
    log.info("IPayInstitutionManager start to init...");
    log.info("IPayInstitutionManager environment:{}", environment);
    log.info("testPrivate:{}", environment.getProperty("test.key"));
    init();
  }
}
原因分析
1)Spring Aware Bean 是什么?

Aware 即”察觉的,注意到的,感知的”,xxxAware 也就是对xxx感知的。Spring 的依赖注入使业务 Bean 对 Spring 容器的存在是没有意识的,但有时候业务 Bean 需要对 Spring 容器有感知,能使用 Spring 容器内置的组件(如 ApplicationContext、BeanFactory 等)。就出现了以下 Aware Bean(具体使用方法不再简介):

  • ApplicationContextAware:获得当前应用上下文
  • EnviromentAware:获得环境变量
  • BeanNameAware:获取容器中bean名称
  • BeanFactoryAware:获得bean工厂
2)从 Spring Bean 的生命周期入手

上面两个例子很容易猜想到 IPayInstitutionManager 进行实例化调用其构造方法过程中,environment 并未执行初始化,以写法二从启动日志中可验证:IPayInstitutionManager 构造函数先于 environment 实例化

在这里插入图片描述

回到 Bean 的生命周期,如下图所示:

在这里插入图片描述

很明显,IPayInstitutionManager 调用构造方法实例化时,Environment 并未完成初始化(图中 EnvironmentAware.setEnvironment 结点)。即若 Spring 未进行 EnvironmentAware.setEnvironment 回调前,Environment 并不能使用,这也是为什么像 Environment、ApplicationContext 等 Spring 内部组件不能直接 @Autowire 注入的原因,必须要实现 Aware Bean

解决方案

结合 Bean 生命周期图,如果要在代码中使用 Environment 等 Spring 组件时,时机要晚于图中 BeanPostProcessor.postProcessBeforeInitialization 方法执行时机即可,比如使用 @PostConstruct 注解,实现 InitializingBean 接口等。这里给出一个示例:

public class IPayInstitutionManager implements EnvironmentAware, InitializingBean {
  
  private Environment environment;
  
  @Override
  public void setEnvironment(Environment environment) {
    this.environment = environment;
  }
  
  @Override
  public void afterPropertiesSet() {
    log.info("testPrivate:{}", environment.getProperty("test.key")); // 正常输出
    init();
  }
}
文章来源:https://blog.csdn.net/zhuqiuhui/article/details/135142747
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。