springboot configuration类注入对象null问题排查

问题描述

今日在开发过程中遇到一个spring configuration类注入失败的问题,具体代码如下:

@Configuration
@Import({TestBean1.class})
public class HelloConfiguration {

    @Resource
    private ApplicationContext applicationContext;


    @Bean
    public TestBean2 testBean2() {
        TestBean1 testBean2 = applicationContext.getBean(TestBean1.class);
        return new TestBean2();
    }

    @Bean
    public TestBeanDefinitionRegistryPostProcessor testBeanDefinitionRegistryPostProcessor() {
        return new TestBeanDefinitionRegistryPostProcessor();
    }


}

执行过程中抛出如下异常NullPointerException:

Caused by: java.lang.NullPointerException
	at com.example.demo.config.HelloConfiguration.testBean2(HelloConfiguration.java:27) ~[classes/:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_211]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_211]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_211]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_211]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:579) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
	... 18 more

排查过程

通过发生npe的位置我们可以推测applicationContext对象没有注入成功。但是代码中我们的确是通过@Resource注解注入了该对象,那么到底是注入失败还是压根没有注入呢?要弄清缘由需要剖析一下spring的内部机制。我们用@Resource注解注入对象的时候,spring是如何处理的呢?

创建HelloConfiguration对象肯定需要AbstractAutowireCapableBeanFactory的doCreateBean方法,

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
		    // 这里实例化对象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
		mbd.resolvedTargetType = beanType;

		// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
				    // 调用后置处理器
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

@Resource是通过CommonAnnotationBeanPostProcessor后置处理器来注入的

通过debug发现在创建HelloConfiguration对象对象时,后置处理器中没有CommonAnnotationBeanPostProcessor。说明spring此时还没有加载到该对象。为什么呢?我们看下HelloConfiguration的代码,下面还创建了一个TestBeanDefinitionRegistryPostProcessor对象,是BeanDefinitionRegistryPostProcessor的实现类,而BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor接口。如果对spring源码有所了解的话应该知道spring容器在启动的时候是先加载BeanFactoryPostProcessor,然后再加载BeanPostProcessor,而CommonAnnotationBeanPostProcessor正是BeanPostProcessor的实现。所以到这里我们可以推测HelloConfiguration的加载过程:

  1. 将configuration中的bean加载到bdf中
  2. 优先实例化BeanDefinitionRegistryPostProcessor对象,因为BeanDefinitionRegistryPostProcessor的factoryBean是HelloConfiguration,所以这里需要实例化HelloConfiguration。但是由于CommonAnnotationBeanPostProcessor还没有加载到spring中,所以无法注入applicationContext对象。
  3. 加载HelloConfiguration的时候会去调用所有@Bean方法,此时方法中使用了applicationContext,于是NPE了。

解决问题

问题的根源是由于BeanDefinitionRegistryPostProcessor的创建导致了HelloConfiguration实例化被前置了。
解决注入问题的一个方式是创建BeanDefinitionRegistryPostProcessor不去提前触发HelloConfiguration对象的实例化。代码如下,仅仅加上一个static修饰符:

@Configuration
@Import({TestBean1.class})
public class HelloConfiguration {

    @Resource
    private ApplicationContext applicationContext;


    @Bean
    public TestBean2 testBean2() {
        TestBean1 testBean2 = applicationContext.getBean(TestBean1.class);
        return new TestBean2();
    }

    @Bean
    public static TestBeanDefinitionRegistryPostProcessor testBeanDefinitionRegistryPostProcessor() {
        return new TestBeanDefinitionRegistryPostProcessor();
    }
}

看到这里有人会问,为什么加了static问题就解决了?

答案是如果spring发现@Bean的方法是static的时候,会在AbstractObjectDefinition中标记到FactoryMethodName。spring在创建bean的时候如果识别到FactoryMethodName就会直接调用static方法,而不必去实例化HelloConfiguration对象。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值