文章目录
- 背景
- 原因分析
- 1)Spring Aware Bean 是什么?
- 2)从 Spring Bean 的生命周期入手
- 解决方案
背景
写业务代码遇到使用 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();
}
}