Maven Failsafe fail with java.lang.NoClassDefFoundError

我开始了一个新项目: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

老实说,我不知道为什么...

有人遇到过这个问题吗?


答案 1

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

事实证明,maven-failafe-plugin不会将目标/类目录添加到类路径中,而是将生成的jar添加到classpath中,这在大多数情况下工作正常。

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

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

maven-failafe-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>

答案 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>

推荐