今天项目组遇到一个问题让我帮忙排查,虽然是一个小众问题,但是涉及到Spring的Bean初始化的相关知识点。整理了一下,分享给大家。
问题描述
注解了 @Service 的对象,其内部注解了 @Autowired 的对象没有被注入,方法在调用时为 null 。并且,排查时发现包括 @PostConstruct 方法没有被调用。(注:以下代码均为示例代码,非业务代码)

问题排查
思路 : @Autowired 在启动时,如果是正常的 BeanFactory 创建的对象,会抛出相应异常。

所以,初步判断为该对象的实例化时机存在问题。
问题跟踪 : 创建构建函数,跟踪问题对象在哪里实例化。


从上图的调用堆栈中,可以非常明确的看到对象 ProblemService 的实例化调用的一个关系。
查看相关代码


找到问题
对象 ProblemService 被一个 BeanFactoryPostProcessor 在处理时通过 Class.forName 装载了,此时会执行装载的对象的静态代码块。

静态代码块,通过 SpringBeans#ConfigurableListableBeanFactory 从 BeanFactory 中加载对象。
那么,为什么加载的对象 ProblemService 的 depInjectBean AutoWired 没有生效,并且 init 方法没有被调用呢?这是因为 BeanFactoryPostProcessor 的机制引起的。
BeanFactoryPostProcessor
在说明 BeanFactoryPostProcessor 之前,我们需要对 Spring 的 Bean 加载处理机制有一定的了解。这里我简要的说明一下。
Spring 的 Bean 加载时有 定义和初始化的区分。而 BeanFactoryPostProcessor 的发生时机在定义完成之后,注意,此时所有的 Bean 只是定义完成,并没有真正的初始化完成 。
那么, BeanFactoryPostProcessor 具体是做什么的呢?
定义:应用工厂的勾子处理,允许自定义修改应用上下文中的Bean定义的属性。 但是,请不在要BeanFactoryPostProcessor时实例化任何一个Bean,否则会导致Bean过早实例早,出现不可预见的情况 。
再强调一次: 不在要BeanFactoryPostProcessor时实例化任何一个Bean,否则会导致Bean过早实例早,出现不可预见的情况 。
原因是, BeanFactoryPostProcessor 的发生时机在所有的 Bean 定义完成之后,实例化之前。也就是在发生时刻,在 Spring容器 中,所有的 Bean 已经定义好了,等待进行实例化。
如果此时,在 BeanFactoryPostProcessor 时机,对 Bean 进行了实例化,而没有完整的注入依赖关系。那么,对于 Spring容器 来说,在 Bean 实例化阶段时,会判断 已经实例化 而跳过这个Bean的处理。
所以,此时对于这个在 BeanFactoryPostProcessor 实例化的 Bean ,其保持着实例化时刻的所有定义。 也就是没有注入的依赖一定是null,并且相关的注解,如@PostConstruct等失效。
问题总结
在上述的流程中,主要出现了二个不合理的应用:
- 在一个 BeanFactoryPostProcessor 中对会一些类进行装载,导致静态代码在非预期时被执行

对于开发和JVM来说,一些类在未被使用时并不需要装载和初始化。所以对开发框架的扩展或者封装,不应该破坏类应该在被需求时才进行装载和初始化,提前装载和初始化应该是有条件的!
- 在类的静态变量中,使用一个可以通过静态方法获取Spring容器中的Bean工具类

对于SpringBean对象的获取时机是要非常注意的。在这种写法中,如果这个类的方法在Spring容器未启动完成之前被调用,就会导致依赖Bean的空指针异常。 如果,在静态方法中需要依赖于Spring容器中的Bean时,需要非常的注意方法的调用时机。如果不是非必须的,不应该用这种方法来实现!