【问题标题】:Maven Failsafe fails with java.lang.NoClassDefFoundErrorMaven Failsafe 因 java.lang.NoClassDefFoundError 而失败
【发布时间】:2018-06-10 20:20:56
【问题描述】:

我开始了一个新项目:PostfixSQLConfig。这是一个简单的 Spring Boot 应用程序,本质上应该为 4 个简单的数据库表提供 CRUD 访问。我为第一个表编写了存储库,并为所述存储库编写了一些基本的集成测试。由于这个特定的表不应该提供更新功能,我将更新功能实现为:

@Override
public void update(@NonNull Domain domain) throws NotUpdatableException {
    throw new NotUpdatableException("Domain entities are read-only");
}

NotUpdatableException 是我的自定义异常类。

此代码的 IT 如下所示:

@Test(expected = NotUpdatableException.class)
public void testUpdate() throws NotUpdatableException {
    val domain = Domain.of("test");

    domainRepository.update(domain);
}

如果从我的 IDE (IntelliJ 2018.2 EAP) 运行此测试,它会正常通过,但运行 mvn verify 会失败:

java.lang.NoClassDefFoundError: com/github/forinil/psc/exception/NotUpdatableException
  at java.lang.Class.getDeclaredMethods0(Native Method)
  at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
  at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
  at java.lang.Class.getMethod0(Class.java:3018)
  at java.lang.Class.getMethod(Class.java:1784)
  at org.apache.maven.surefire.util.ReflectionUtils.tryGetMethod(ReflectionUtils.java:60)
  at org.apache.maven.surefire.common.junit3.JUnit3TestChecker.isSuiteOnly(JUnit3TestChecker.java:65)
  at org.apache.maven.surefire.common.junit3.JUnit3TestChecker.isValidJUnit3Test(JUnit3TestChecker.java:60)
  at org.apache.maven.surefire.common.junit3.JUnit3TestChecker.accept(JUnit3TestChecker.java:55)
  at org.apache.maven.surefire.common.junit4.JUnit4TestChecker.accept(JUnit4TestChecker.java:53)
  at org.apache.maven.surefire.util.DefaultScanResult.applyFilter(DefaultScanResult.java:102)
  at org.apache.maven.surefire.junit4.JUnit4Provider.scanClassPath(JUnit4Provider.java:309)
  at org.apache.maven.surefire.junit4.JUnit4Provider.setTestsToRun(JUnit4Provider.java:189)
  at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:132)
  at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:379)
  at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:340)
  at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125)
  at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:413)
Caused by: java.lang.ClassNotFoundException: 
com.github.forinil.psc.exception.NotUpdatableException
  at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
  at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
  ... 18 more

我真的不知道为什么......

有人遇到过这个问题吗?

【问题讨论】:

    标签: java maven spring-boot junit4 maven-failsafe-plugin


    【解决方案1】:

    我想通了,所以我正在回答我自己的问题,以防其他人有同样的问题。

    事实证明,maven-failsafe-plugin 并没有将 target/classes 目录添加到类路径中,而是添加了生成的 jar,这在大多数情况下都可以正常工作。

    然而,当涉及到 Spring Boot 时,生成的 jar 包含 Spring Boot 自定义类加载器类,而不是 target/classes 目录的内容,这些内容被移动到目录 BOOT-INF/classes。由于 maven-failsafe-plugin 使用“常规”类加载器,它只加载 Spring Boot 类加载器类,首先失败,它应该使用项目类之一。

    要在 Spring Boot 项目中运行 IT 测试,必须从依赖项中排除打包的 jar,并添加原始的、未修改的 jar 或 target/classes 目录,这就是我所做的。

    maven-failsafe-plugin 和 Spring Boot 的正确配置是:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.21.0</version>
        <executions>
            <execution>
                <goals>
                    <goal>integration-test</goal>
                     <goal>verify</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <classpathDependencyExcludes>
                <classpathDependencyExcludes>${groupId}:${artifactId}</classpathDependencyExcludes>
            </classpathDependencyExcludes>
            <additionalClasspathElements>
                <additionalClasspathElement>${project.build.outputDirectory}</additionalClasspathElement>
            </additionalClasspathElements>
        </configuration>
    </plugin>
    

    【讨论】:

    • 经过 3 天的搜索,你救了我 :)
    • 收到以下警告:[WARNING] The expression ${groupId} is deprecated. Please use ${project.groupId} instead. [WARNING] The expression ${artifactId} is deprecated. Please use ${project.artifactId} instead. - 可能需要改用这些表达式。
    • 你也让我解封了!谢谢!
    • 这个答案在故障安全插件版本 3.0.0-M5 上对我不起作用,而是需要以下配置部分...&lt;configuration&gt; &lt;classesDirectory&gt;${project.build.outputDirectory}&lt;/classesDirectory&gt; &lt;/configuration&gt;
    【解决方案2】:

    另一个似乎可行的选项是将分类器添加到 spring-boot-maven-plugin 配置中。这会导致 SpringBoot 单独保留“默认”构建目标 jar,而是创建附加了分类器名称的 SpringBoot uber jar。

    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
              <classifier>sb-executable</classifier>
        </configuration>
    </plugin>
    

    【讨论】:

    • 我认为这个解决方案比那些需要我弄乱 Failsafe 类路径设置的解决方案干净得多
    • 应该是公认的答案。用这一行配置修复了很多问题。
    • 这个解决方案在本地工作,但是因为正确的输出 jar 文件现在有一个后缀,所以弄乱了我的 CI。
    • @KrisG - 正确。如果需要部署应用程序,则需要更新 CI 以使用带有后缀的 jar,因为这将是 Springboot 可运行/uber jar。
    【解决方案3】:

    这适用于我的 spring-boot 项目和failsafe plugin version - 3.0.0-M5

    <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-failsafe-plugin</artifactid>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <classesdirectory>${project.build.outputDirectory}</classesdirectory>
    </configuration>
    </plugin>
    

    博客引用/漂亮的解释 - https://bjoernkw.com/2020/12/06/using-maven-failsafe-with-spring-boot/

    如果您想了解 project.build.outputDirectory 是什么 - Maven project.build.directory

    谢谢!

    【讨论】:

      猜你喜欢
      • 2021-03-06
      • 1970-01-01
      • 2012-08-30
      • 2019-06-23
      • 2016-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多