最近封装了一个Redis基础组件包,其他在项目引入时,报了一个 java.lang.ClassNotFoundException 异常,缺少 JacksonException 类的定义,以下记录整体排错与解决过程。

报错信息如下:

java.util.ServiceConfigurationError: javax.cache.spi.CachingProvider: Provider org.redisson.jcache.JCachingProvider could not be instantiated
    at java.util.ServiceLoader.fail(ServiceLoader.java:232)
    at java.util.ServiceLoader.access$100(ServiceLoader.java:185)
    at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:384)
    at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
    at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
    at javax.cache.Caching$CachingProviderRegistry$1.run(Caching.java:448)
    at javax.cache.Caching$CachingProviderRegistry$1.run(Caching.java:442)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.cache.Caching$CachingProviderRegistry.getCachingProviders(Caching.java:442)
    at javax.cache.Caching$CachingProviderRegistry.getCachingProviders(Caching.java:410)
    at javax.cache.Caching.getCachingProviders(Caching.java:187)
    at org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration$JCacheProviderAvailableCondition.getMatchOutcome(JCacheCacheConfiguration.java:160)
    at org.springframework.boot.autoconfigure.condition.AbstractNestedCondition$MemberOutcomes.getConditionOutcome(AbstractNestedCondition.java:194)
    at org.springframework.boot.autoconfigure.condition.AbstractNestedCondition$MemberOutcomes.<init>(AbstractNestedCondition.java:188)
    at org.springframework.boot.autoconfigure.condition.AbstractNestedCondition$MemberConditions.lambda$getMatchOutcomes$0(AbstractNestedCondition.java:168)
    at java.util.Map.forEach(Map.java:630)
    at java.util.Collections$UnmodifiableMap.forEach(Collections.java:1507)
    at org.springframework.boot.autoconfigure.condition.AbstractNestedCondition$MemberConditions.getMatchOutcomes(AbstractNestedCondition.java:168)
    at org.springframework.boot.autoconfigure.condition.AbstractNestedCondition$MemberMatchOutcomes.<init>(AbstractNestedCondition.java:78)
    at org.springframework.boot.autoconfigure.condition.AbstractNestedCondition.getMatchOutcome(AbstractNestedCondition.java:63)
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47)
    at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator.shouldSkip(ConfigurationClassBeanDefinitionReader.java:469)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:131)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:120)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:331)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:236)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:280)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:96)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
    at com.narwal.scm.ScmBusinessApplication.main(ScmBusinessApplication.java:33)
Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/JacksonException
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
    at java.lang.Class.getConstructor0(Class.java:3075)
    at java.lang.Class.newInstance(Class.java:412)
    at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:380)
    ... 36 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.core.JacksonException
    at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    ... 41 common frames omitted

版本冲突问题定位

从报错信息看,这是一个典型的由于引入依赖包之间版本冲突导致的问题。必定是有多个公共包同时依赖了另外一个基础包,而项目引用的基础包生效版本,低于某个公共包要求的最低版本导致的。

这种报错一般有两种情况:

  1. 项目中直接定义的基础包版本过低;
  2. 项目中未直接定义基础包的版本,引入的多个公共包中定义的版本间存在冲突。

进一步分析报错日志和 Maven 依赖情况定位问题:

查找缺失的类:

从报错日志最下面找到缺失的类 Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.core.JacksonException。

从类的包名路径可以推测是 jackson-core 中定义的依赖。查看项目中引入的 jackson-core 版本是 2.11.0,直接将其升级为 2.12.0,再次查找 JacksonException,可以找到类的定义:

image.png

定位引用的类:

从最上面找到引用的类 Provider org.redisson.jcache.JCachingProvider could not be instantiated ,拉出 JCachingProvider 的实现,可以找其是 Redisson 中定义的类:

image.png

版本冲突问题分析:

那么现在问题就很明确了,是 Redisson 中引用 jackson-core 中的类不存在导致,只需要分析这两个包的依赖版本情况即可。

首先查看项目中对 Jackson 的版本依赖,依赖的公共包很多,优先关注 Redisson 直接对 Jackson 的依赖:

image.png

跳到 Redisson 的 pom 包中,可以看到其对 Jackson 的依赖:

image.png

image.png

没有定义版本,进一步去 Redisson 的父包中查找,可以找到对 jackson 版本的定义为 2.12.1。其中 jackson-bom 是 jackson 的依赖管理父包,其中统一定义了各个 Jackson 组件的版本情况。

image.png

找到这里可以明确是项目中最终引入的 Jackson 版本低于 Redisson 中定义的版本。

接下来就具体分析是刚开始提到的两种情况的哪种导致:

  1. 首先查找项目和项目 parent 中对 jackson-core 的直接依赖,发现都没有。
  2. 继续分析项目的 Maven 依赖链图,这一步按照 Maven 依赖优先级的顺序依次查找。

在所有依赖中,注意到 spring-boot-starter-web 包对 Jackson 的依赖,这个是项目的基础父包,优先排查。

image.png

按照上面的思路,依次跳转的各个链路上的 source pom 上查找对 jackson 的依赖和版本定义情况。最终从 spring-boot-starter-web 的父包 spring-boot-starter-parent 中找到 spring-boot-dependencies,其中定义了对 jackson-bom 的依赖版本为 2.11.0。

image.png

image.png

由于 spring-boot-starter-web 是项目的父包,所以项目对它的 Maven 依赖优先级更高,最终整个项目引入的 jackson-core 的 2.11.0 版本。

导入基础包前后差异分析

版本冲突的问题解决了,突然想到项目之前就引入并使用了 Redisson 组件,并且版本也是3.15.5,为什么之前启动不会报错呢?为了弄清这个疑问,决定再进一步分析报错信息:

拉出 JCachingProvider 的实现,具体报错的方法为 loadConfig。这种初始化类时缺少类定义的报错,与初始化类该方法的调用无关,与类的初始化调用有关。

image.png

向下分析报错日志,逐行点击引用关系链:

image.png

从SpringBoot初始化类入手,点击找到方法:at org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration$JCacheProviderAvailableCondition.getMatchOutcome(JCacheCacheConfiguration.java:160)

image.png

报错的方法为 SpringBoot 加载条件判断的接口和方法,直接找 JCacheProviderAvailableCondition 类被引用充当 Condition 的地方:

image.png

往上再找 JCacheAvailableCondition 被引用的地方:

image.png

最终找到 SpringBoot 的自动装配初始化类 JCacheCacheConfiguration,查找类的引用,可以在spring-autoconfigure-metadata.properties 中找到类的定义:

image.png

排查到这里已经找到类的初始化入口,现在的关键是调试 JCacheCacheConfiguration 类上定义的各个Condition 是否成立。重点从 @Conditional 中的两个条件入手。

首先调试 CacheCondition 中的条件,对比引入 Redis 公共包前后的执行情况,发现引入前后都执行成功:

image.png

接着调试 JCacheAvailableCondition 中的判断条件,执行情况如下。不同点为引入 Redis 公共包后执行了 JCacheProviderAvailableCondition 中的判断逻辑(方法截图在上面有提到)。

image.png

执行到这里未能进一步确认是什么原因导致了这种情况的出现,推测是 Redis 公共包的引入导致 JCacheAvailableCondition 中条件的加载顺序发生变化导致。@ConditionalOnSingleCandidate(CacheManager.class) 这个条件推测为固定成立。

结论进一步推导,后续待补充。


东瓜
18 声望3 粉丝