【问题标题】:NoClassDefFoundError when using Spring Boot modules loaded from PropertiesLoader by IntelliJ使用 IntelliJ 从 PropertiesLoader 加载的 Spring Boot 模块时出现 NoClassDefFoundError
【发布时间】:2019-09-17 13:30:37
【问题描述】:

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

-Dloader.main=com.mycompany.App
-Dloader.path="C:\dir\some-module.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 Auto-Configure 捆绑到核心 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 应用程序”运行,它可以运行,但作为“应用程序”运行更方便,从那时起我可以进行热替换,我不需要为每一次改变。我也想知道我自己的理解。

【问题讨论】:

  • 使用你的 github 我已经在这里失败了:Unable to open nested entry 'org/h2/util/data.zip'. It has been compressed and nested jar files must be stored without compression. Please check the mechanism used to create your executable jar file
  • @MạnhQuyếtNguyễn 使用哪个运行配置运行它时?

标签: java spring spring-boot intellij-idea classloader


【解决方案1】:

我检查了 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\"

【讨论】:

  • “并且在类路径中,jdbc-1.0-SNAPSHOT.jar 不存在(似乎包含所需类的任何 jar 都不存在)”那是因为它不需要在类路径上.它在 Spring 的加载器路径上。
  • 在我看来,问题的出现是因为使用 org.springframework.boot.loader.PropertiesLauncher 作为来自 jar 的主类运行“应用程序”会在加载程序路径和类路径之间产生影响,这使得它成为必须将所需的 jar 包含到类路径中。我也修正了我的答案,需要的罐子是 HikariCP-3.2.0.jar。
  • 我找到了应该用于作为应用程序运行的配置。我已将其添加到我的答案中。
  • 我也认为问题出现是因为“C:\Java\jdbc-autoconfigure-failure\core\target\classes”在类路径中而不是在加载器路径中
  • 这是一种解决方法,但它并不能真正回答为什么这个基本问题。就像我在我的问题中所说的那样,即使没有 -classpath (因为我评估了 classLoader.loadClass(...) ),该类显然在类路径上。问题在于它是从哪个类加载器加载的。
猜你喜欢
  • 2019-04-15
  • 2015-10-08
  • 2016-02-16
  • 2021-05-12
  • 2016-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-01
相关资源
最近更新 更多