Maven构建可执行Shaded Jar遇JavaLoggingSystem类找不到异常求助
我通过Maven创建了一个包含所有依赖的可执行Shaded Jar,想要用命令java -jar integration-tests.jar启动,但执行时抛出了如下异常:
Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) at org.springframework.boot.loader.Launcher.launch(Launcher.java:50) at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51) Caused by: java.lang.IllegalStateException: java.lang.ClassNotFoundException: org.springframework.boot.logging.java.JavaLoggingSystem at org.springframework.boot.logging.LoggingSystem.get(LoggingSystem.java:130) at org.springframework.boot.logging.LoggingSystem.get(LoggingSystem.java:117) at com.ciscospark.integration.JUnitTestRunner.main(JUnitTestRunner.java:41) ... 8 more Caused by: java.lang.ClassNotFoundException: org.springframework.boot.logging.java.JavaLoggingSystem at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at org.springframework.util.ClassUtils.forName(ClassUtils.java:250) at org.springframework.boot.logging.LoggingSystem.get(LoggingSystem.java:125) ... 10 more
奇怪的是,解压integration-tests.jar后,在extracted/BOOT-INF/lib/spring-boot-1.4.7.RELEASE.jar中确实能找到./org/springframework/boot/logging/java/JavaLoggingSystem.class文件。
附上我的Maven配置:
<build> <finalName>integration-tests</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.integration.JUnitTestRunner</mainClass> <finalName>integration-tests</finalName> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.20.1</version> <configuration> <testClassesDirectory>${project.build.outputDirectory}</testClassesDirectory> <testSourceDirectory>${project.build.sourceDirectory}</testSourceDirectory> </configuration> <executions> <execution> <id>integration-tests</id> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <version>1.6.2</version> <configuration> <container> <containerId>tomcat7x</containerId> </container> <configuration> <type>standalone</type> <properties> <cargo.servlet.port>${tomcat.server.port}</cargo.servlet.port> <cargo.rmi.port>${tomcat.rmi.port}</cargo.rmi.port> <cargo.tomcat.ajp.port>${tomcat.ajp.port}</cargo.tomcat.ajp.port> </properties> </configuration> <deployables> <deployable> <groupId>com.me</groupId> <artifactId>my-server</artifactId> <type>war</type> <properties> <context>/</context> </properties> <pingUrlPath>/ping</pingUrlPath> </deployable> </deployables> </configuration> <executions> <execution> <id>start-server</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop-server</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
请问我哪里操作出错了?
这个问题的核心是Spring Boot的类加载机制和你当前的Jar构建方式不匹配,同时还有一个容易忽略的细节问题,下面逐一拆解:
1. 先揪出最明显的错误:主类包名不一致
你在Maven配置里指定的主类是com.integration.JUnitTestRunner,但异常栈里显示的类路径是com.ciscospark.integration.JUnitTestRunner,包名差了ciscospark这一截!这会导致Spring Boot加载时找不到正确的主类,间接引发后续的类加载异常,先把这个包名修正一致再说。
2. 混淆了Spring Boot Repackage和Shade打包的区别
你提到要构建"包含所有依赖的Shaded Jar",但当前用的是spring-boot-maven-plugin的repackage目标——这个插件生成的是Spring Boot专属的可执行Jar,它把依赖放在BOOT-INF/lib下,并且用Spring自己的JarLauncher来加载类。如果你的JUnitTestRunner没有通过Spring Boot的标准启动流程初始化,直接调用Spring的LoggingSystem类,就会因为类加载器未完全初始化,找不到嵌套Jar里的类。
针对场景的修复方案
方案一:按Spring Boot标准流程启动(推荐)
把你的JUnitTestRunner改成标准的Spring Boot应用类,让Spring负责初始化类加载器:
@SpringBootApplication public class JUnitTestRunner implements CommandLineRunner { public static void main(String[] args) { // 让Spring Boot完整初始化上下文 SpringApplication.run(JUnitTestRunner.class, args); } @Override public void run(String... args) throws Exception { // 在这里执行你的集成测试逻辑 } }
这样Spring Boot会正确加载BOOT-INF/lib下的所有依赖类,包括JavaLoggingSystem。
方案二:用maven-shade-plugin构建真正的Shaded Jar
如果你不需要Spring Boot的特性,只是想要一个包含所有依赖的普通可执行Jar,那就改用maven-shade-plugin,同时移除spring-boot-maven-plugin的repackage执行:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <!-- 这里要填正确的主类全路径 --> <mainClass>com.ciscospark.integration.JUnitTestRunner</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
这个插件会把所有依赖类合并到同一个Jar里,用JDK标准的类加载器就能加载所有类。
内容的提问来源于stack exchange,提问作者Shane Gannon

