最近新接手一个项目,在日常环境启动的时候报错启动不了,查看日志发现是由于@Value的值为null,导致启动报错
我们先来还原一下事故现场
自定义一个BeanDefinitionRegistryPostProcessor来模拟Mybatis的MapperScannerConfigurer
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
private ConsoleApi consoleApi;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
System.out.println("this is MyBeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry");
System.out.println("MyBeanDefinitionRegistryPostProcessor: "+ consoleApi);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException {
System.out.println("this is MyBeanDefinitionRegistryPostProcessor#postProcessBeanFactory");
}
public ConsoleApi getDivisionDO() {
return consoleApi;
}
public void setDivisionDO(ConsoleApi consoleApi) {
this.consoleApi = consoleApi;
}
}在配置类中注入这个Bean
@Configuration
public class DsConfig {
// 这里通过@Value注入配置信息
@Value("spring.demo.hsf.env")
private String env;
@Bean
public ConsoleApi divisionDO(){
System.out.println("DsConfig env ===========>>>> " + env);
if("daily".equalsIgnoreCase(env)){
return new ConsoleApi();
}else {
throw new RuntimeException("环境不支持");
}
}
@Bean
public MyBeanDefinitionRegistryPostProcessor myBeanDefinitionRegistryPostProcessor(ConsoleApi ConsoleApi){
MyBeanDefinitionRegistryPostProcessor myBeanDefinitionRegistryPostProcessor = new MyBeanDefinitionRegistryPostProcessor();
myBeanDefinitionRegistryPostProcessor.setDivisionDO(ConsoleApi);
return myBeanDefinitionRegistryPostProcessor;
}
}启动应用

可以看到启动报错,这里的env取值为null

很是疑惑,这里我明明配置了env=daily,为啥取不到值呢?
然后开始面向百度编程.......
首先我将自定义个这个BeanDefinitionRegistryPostProcessor注释掉,发现应用能够成功启动,那么问题十有八九就是这个自定义的BeanDefinitionRegistryPostProcessor导致的
BeanDefinitionRegistryPostProcessor是一个BeanFactoryPostProcessor 那么这个Bean是在什么时候被示例化的呢,咱们来debug一下
Spring容器启动
org.springframework.context.support.AbstractApplicationContext#refresh
{
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 这里调用Spring容器的BeanFactoryPostProcessor
// 注意这里调用的时候还没有初始化自定义的BeanFactoryPostProcessor,而是创建BeanFactory的是Spring 自己New出来的三个BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}接着往里面跟一下代码

这里Spring会两次调用获取BeanDefinitionRegistryPostProcessor类型的Bean名称并实例化Bean
第一次调用获取到了是框架自身的ConfigurationClassPostProcessor 加载应用中所有的BeanDefinition
第二次调用是获取自定义的BeanDefinitionRegistryPostProcessor并实例化这些Bean,就会通过代理的方式处理@Bean注入的Bean,这个时候其实Spring还没有初始化其他的Bean包括DsConfig所以这个时候@Value还没有被处理,那么env的值当然为nulll了
现在问题找到了,那么该怎么去解决呢?
问题的关键是在实例化MyBeanDefinitionRegistryPostProcessor的时候,DsConfig还没有被实例化出来,那能不能在实例化MyBeanDefinitionRegistryPostProcessor之前就把DsConfig给实例化出来呢?顺着这思路我给出了下面的解决方法
@Configuration
public class DsConfig implements ApplicationContextAware, InitializingBean {
// 这里通过@Value注入配置信息
//@Value("${spring.demo.hsf.env}")
private String env;
private ApplicationContext applicationContext;
@Bean
public ConsoleApi consoleApi(DsConfig dsConfig) {
System.out.println("DsConfig env ===========>>>> " + env);
if ("daily".equalsIgnoreCase(env)) {
return new ConsoleApi();
} else {
throw new RuntimeException("环境不支持");
}
}
@Bean
public MyBeanDefinitionRegistryPostProcessor myBeanDefinitionRegistryPostProcessor(ConsoleApi ConsoleApi){
MyBeanDefinitionRegistryPostProcessor myBeanDefinitionRegistryPostProcessor = new MyBeanDefinitionRegistryPostProcessor();
myBeanDefinitionRegistryPostProcessor.setDivisionDO(ConsoleApi);
return myBeanDefinitionRegistryPostProcessor;
}
@Override
public void afterPropertiesSet() throws Exception {
Environment environment = applicationContext.getEnvironment();
String env = environment.getProperty("spring.demo.hsf.env");
this.env = env;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}咱们再来启动

这里已经能够获取到配置的变量 env=daily了,应用也成功启动了
如果你有更好的解决方法,欢迎一起探讨
参考:Spring源码之@Configuration原理 - 曹自标 - 博客园
spring @Value注解原理梳理及自定义实现@MyValue注解实例_mapeng765441650的博客-CSDN博客