在这篇文章中我想分享我最近遇到的一个问题,我想您可能会感兴趣。
出了什么问题?
测试环境和生产环境的源代码是相同的,本地和测试环境都运行良好,只有生产环境运行的服务类的NPE加载失败,这是为了新的需求而添加的。该类继承自Customize包(自研工具包)的接口。
项目结构
Customize 的设计依赖于 Spring 来管理 API 和服务。通过Spring自动扫描,初始化类时会加载ApiEnhancer和ServiceEnhancer的公共类,并通过ApplicationContext获取实例。调试发现加载它们时,ApplicationContext为null,还没有初始化。是在setApplicationContext方法中通过实现ApplicationContextAware来初始化的,所以推测setApplicationContext方法还没有被执行。
在ApplicationContextProvider类的加载过程中,静态方法在初始化阶段被加载到方法区中。然而,当使用ApplicationContext获取bean的实例时,静态方法是直接通过类名调用的。只要API是在ApplicationContextProvider在堆中分配内存并实例化之前创建的,就不会调用setApplicationContext方法进行初始化。
applicationContextProvider使用注解@Component,Api使用注解@RestController,两个类在同一个路径下。然而,当 Spring 扫描并加载它们时,没有特定的顺序,这意味着这两个类中的每一个都可能在另一个类之前创建。在生产环境中,Api是先于applicationContextProvider创建的,导致通过类名直接调用静态方法获取bean时,applicationContext没有被初始化。
解决方案
如果没有执行ApplicationContextAware接口的实现类的setApplicationContext方法,首先检查该实现类是否设置了延迟加载或者项目是否配置了全局延迟加载。
在这个项目中,问题是由ApiEnhancer和ApplicationContextAware接口的实现类的创建顺序引起的,而Api类只是一个常规的RESTful API,处理业务逻辑,可以在前端调用时加载-结束页。因此,改为延迟加载解决了问题。
建议在加载顺序方面尽量减少类之间的依赖关系。如果不可避免,可以使用延迟加载,或者使用@DependsOn、@Order、@Priority等注解来控制bean的加载顺序
为什么生产环境和测试环境的创建顺序不同?
我们来调试一下Spring的扫描过程。
从ClassPathBeanDeterminationScanner的scan方法开始。
doScan方法
ClassPathScanningCandidateComponentProvider 的 scanCandidateComponents 方法
PathMatchingResourcePatternResolver 的 findAllClassPathResources 方法
doFindPathMatchingJarResources 方法
JarFile是Java标准库中java.util.jar包下的一个类,它继承并扩展了ZipFile。 jarFile.entries() 返回一个名为 JarEntryIterator 的迭代器。
JarExitIterator 是 迭代器模式 的实现,它迭代 JarFile 父类的条目。
ZipFile 的 Entry 方法返回一个名为 ZipExitIterator 的迭代器。
ZipExitIterator 的 nextElement 方法调用 next 方法,后者又调用 getNextEntry 方法。
getNextEntry 是一个原生方法。
native 方法是用非 Java 语言实现的,并在 Java 虚拟机内调用来实现底层功能,这可能会因环境(操作系统或 JDK 版本)而异。 JAR包本身没有顺序,所以实际的遍历顺序可能会根据不同的JAR打包工具和环境而有所不同。
以上是applicationContextAware接口的setApplicationContext方法执行问题,获取Spring bean失败的详细内容。更多信息请关注PHP中文网其他相关文章!