java - 使用 IntelliJ 从 PropertiesLoader 加载的 Spring Boot 模块时出现 NoClassDefFoundError

标签 java spring spring-boot intellij-idea classloader

我有一个应用程序,它有一个 Spring Boot 核心,可以在运行时添加可选模块(使用 PropertiesLoader)。

-Dloader.main=com.mycompany.App
-Dloader.path="C:\dir\some-module.jar"

模块被打包为 fat jar,因此它们的所有依赖项都与它们捆绑在一起。我为此使用了阴影插件。

其中一个附加模块依赖于 JDBC,当我解压缩它的 JAR 时,我可以看到存在 Hikari 内容。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

当我从 JAR 运行应用程序时,一切正常。但是,当我从 IntellIJ 的类路径运行时,其中一个 Hikari 类无法加载。

java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:64) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator.shouldSkip(ConfigurationClassBeanDefinitionReader.java:447) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:128) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:327) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:705) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:531) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at com.mycompany.App.main(App.java:11) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) ~[spring-boot-loader-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) ~[spring-boot-loader-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:50) ~[spring-boot-loader-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:593) ~[spring-boot-loader-2.1.3.RELEASE.jar:2.1.3.RELEASE]
Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:686) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:583) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:568) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.util.ReflectionUtils.getUniqueDeclaredMethods(ReflectionUtils.java:626) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705) ~[na:na]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryMethod(AbstractAutowireCapableBeanFactory.java:738) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:679) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:647) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1518) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1023) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.boot.autoconfigure.condition.BeanTypeRegistry.addBeanTypeForNonAliasDefinition(BeanTypeRegistry.java:195) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.BeanTypeRegistry.addBeanTypeForNonAliasDefinition(BeanTypeRegistry.java:159) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.BeanTypeRegistry.addBeanType(BeanTypeRegistry.java:152) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.BeanTypeRegistry.updateTypesIfNecessary(BeanTypeRegistry.java:140) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na]
    at org.springframework.boot.autoconfigure.condition.BeanTypeRegistry.updateTypesIfNecessary(BeanTypeRegistry.java:135) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.BeanTypeRegistry.getNamesForType(BeanTypeRegistry.java:97) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.collectBeanNamesForType(OnBeanCondition.java:298) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanNamesForType(OnBeanCondition.java:289) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanNamesForType(OnBeanCondition.java:278) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchingBeans(OnBeanCondition.java:189) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchOutcome(OnBeanCondition.java:138) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
    ... 24 common frames omitted
Caused by: java.lang.NoClassDefFoundError: com/zaxxer/hikari/HikariDataSource
    at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[na:na]
    at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3171) ~[na:na]
    at java.base/java.lang.Class.getDeclaredMethods(Class.java:2314) ~[na:na]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:668) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    ... 46 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.zaxxer.hikari.HikariDataSource
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    ... 50 common frames omitted

当我在 org.springframework.boot.loader.Launcher 中放置一个断点时

protected void launch(String[] args) throws Exception {
    JarFile.registerUrlProtocolHandler();
    ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives());
    this.launch(args, this.getMainClass(), classLoader); //breakpoint 
}

我可以看到 LaunchedURLClassLoader 已正确创建(即它具有对模块 JAR 的引用)并且当我评估时

classLoader.loadClass("com.zaxxer.hikari.HikariDataSource")

在调试器中,我可以看到类可以从这里正常加载。

但是,当加载 DataSourceJmxConfiguration$Hikari 时,它会使用父类加载器加载,因为 Spring Boot 自动配置已捆绑到核心 Spring Boot 应用程序中。当 Spring 尝试调用 getDeclaredMethods 时,它会崩溃,因为 HikariDataSource 需要由子类加载器加载,而父类不知道它的任何子类。

HikariDataSource 肯定在类路径上,因为 DataSourceJmxConfiguration$Hikari 以它存在为条件

@Configuration
@ConditionalOnClass({HikariDataSource.class})
@ConditionalOnSingleCandidate(DataSource.class)
static class Hikari {
    //...
}

我无法确定直接从 JAR 运行有什么不同,以及为什么这对 IntelliJ 正在做的事情有效。我可以看到初始类加载器是以相同的方式创建的。

不幸的是,这个问题不适合在此处发布可重现的示例(我可以但您必须自己构建整个项目结构)。相反,我创建了 a MCVE on GitHub - 只有 5 个文件,其中 3 个是 POM。

这些类的加载方式有何不同,我该如何解决?

我可以继续作为“JAR 应用程序”运行,这是可行的,但作为“应用程序”运行更方便,从那以后我可以进行热替换,我不需要为它构建新的 JAR每一个变化。我也想知道我自己的理解。

最佳答案

我已经检查了 Intellij 运行的 java 命令(它写在运行窗口的第一行,你可以复制它并检查它),当你将它作为应用程序运行时,Intellij 以编程方式添加 -classpath 选项java命令。在类路径中,jdbc-1.0-SNAPSHOT.jar 不存在(似乎包含所需类的任何 jar)。所以我添加了这个配置你的配置(“测试(失败)”一个)

-Dloader.main=com.mycompany.App
-Dloader.path=C:\Java\jdbc-autoconfigure-failure\jdbc\target\shaded\jdbc-1.0-SNAPSHOT.jar
-classpath $Classpath$

它奏效了。因为似乎选项 -classpath $Classpath$ 使用项目 -classpath VM 选项重新加载类路径并加载 HikariCP-3.2.0.jar。您可以使用该解决方案或在依赖项选项卡的“核心”模块设置中添加 jdbc-1.0-SNAPSHOT.jar。

编辑: 这是应该用于作为应用程序运行的配置。首先在工作目录下的lib文件夹下添加spring-boot需要的所有库,你可以在core-1.0-SNAPSHOT.jar中的BOOT-INF\lib中找到spring-boot需要的所有jar。在这个例子中,我将所有需要的 jar 复制到 C:\Java\jdbc-autoconfigure-failure\core\lib

-Dloader.main=com.mycompany.App
-Dloader.path="C:\Java\jdbc-autoconfigure-failure\core\lib,C:\Java\jdbc-autoconfigure-failure\core\target\classes,C:\Java\jdbc-autoconfigure-failure\jdbc\target\shaded\jdbc-1.0-SNAPSHOT.jar"
-classpath "C:\Java\jdbc-autoconfigure-failure\core\lib\spring-boot-loader-2.1.3.RELEASE.jar"

编辑: 发现问题

-Dloader.main=com.mycompany.App
-Dloader.path=\"C:\Java\jdbc-autoconfigure-failure\jdbc\target\shaded\jdbc-1.0-SNAPSHOT.jar\"

关于java - 使用 IntelliJ 从 PropertiesLoader 加载的 Spring Boot 模块时出现 NoClassDefFoundError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57975361/

相关文章:

java - 在 hibernate5.1 和 spring 4.1.6 集成中找不到 Hibernatetemplate 的 opensession() 异常

spring-boot - 不能在 Spring Boot 2(版本 2.0.0.M7)中包含 Prometheus 指标

java - 获取ListenableFuture后立即get()

java - 是否可以通过 Keycloak 中的 REST API 检查用户是否具有 Activity/有效 session ?

java - Spring Cron 表达式随时不执行作业?

java - 如何将tomcat访问日志发送到kafka

spring - Keycloak Spring Boot 适配器和匿名资源

java - 使用 OAuth2 从 Spring Security 自定义身份验证错误

java - 在这种情况下,使用 BufferedReader 是否可以获得任何性能提升?

mysql - Spring批处理xlxs文件到MySql数据库