Maven Shade构建的Fat Jar通过ServiceLoader加载时依赖缺失问题
Maven Shade打包Fat Jar后ServiceLoader加载依赖失效问题
问题现象
用Maven Shade插件构建包含所有依赖的Fat Jar后:
- 直接通过
java -jar auth-plugin-jar-with-dependencies.jar运行或在IntelliJ启动主类时,依赖正常可用; - 但通过Java ServiceLoader加载该Jar时,依赖无法被识别:
- Spring(非Spring Boot)ComponentScan找不到对应Bean,报错:
No qualifying bean of type 'se.plugin.auth.integration.example.authservice.GetAuthorizationService' available; - 手动声明Bean后,出现Jakarta XML Binding-API缺失错误:
Implementation of Jakarta XML Binding-API has not been found on module path or classpath;
- Spring(非Spring Boot)ComponentScan找不到对应Bean,报错:
- 临时可行的启动方式是
java -cp simulator.jar;auth-plugin-with-dependencies.jar Main,但单独运行java -jar simulator.jar时问题复现。
问题根源
- Shade插件配置缺陷:当前配置未合并Spring的
META-INF/spring.handlers、META-INF/spring.schemas等资源,也未处理ServiceLoader的META-INF/services文件合并,导致Spring无法扫描Bean、ServiceLoader无法正确加载实现类; - 类加载器隔离问题:使用
URLClassLoader加载插件Jar时,Spring上下文类加载器仍为主程序类加载器,无法访问插件Jar中的依赖类; - 依赖排除错误:Shade配置中排除了
jakarta.activation:jakarta.activation-api和jakarta.xml.ws:jakarta.xml.ws-api,而这些是Jakarta XML Binding的必要依赖,导致运行时缺失。
解决方案
1. 修正Maven Shade插件配置
添加资源转换器,合并Spring配置文件、ServiceLoader文件,移除不必要的依赖排除:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <finalName>auth-plugin-jar-with-dependencies</finalName> <transformers> <!-- 合并META-INF/services文件,支持ServiceLoader --> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> <!-- 合并Spring配置文件 --> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.tooling</resource> </transformer> <!-- Manifest配置 --> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>test</mainClass> <manifestEntries> <Multi-Release>true</Multi-Release> </manifestEntries> </transformer> </transformers> <createDependencyReducedPom>false</createDependencyReducedPom> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>module-info.class</exclude> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> <!-- 移除不必要的依赖排除,若主程序已包含可保留 --> <artifactSet> <excludes> <!-- <exclude>jakarta.activation:jakarta.activation-api</exclude> --> <!-- <exclude>jakarta.xml.ws:jakarta.xml.ws-api</exclude> --> </excludes> </artifactSet> </configuration> </execution> </executions> </plugin>
2. 调整插件加载时的类加载器
加载插件时临时切换线程上下文类加载器为插件的URLClassLoader,确保Spring能扫描到插件类:
public static List<AuthPlugin> loadPlugins(Class<AuthPlugin> pluginClazz) { ClassLoader baseClassLoader = pluginClazz.getClassLoader(); File targetDirectory = new File("../auth/target"); File pluginJarFile = new File(targetDirectory, "auth-plugin-jar-with-dependencies.jar"); try { URL jarUrl = pluginJarFile.toURI().toURL(); URLClassLoader jarClassLoader = new URLClassLoader(new URL[]{jarUrl}, baseClassLoader); // 临时替换线程上下文类加载器 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(jarClassLoader); try { ServiceLoader<AuthPlugin> serviceLoader = ServiceLoader.load(pluginClazz, jarClassLoader); serviceLoader.reload(); return serviceLoader.stream() .map(ServiceLoader.Provider::get) .collect(Collectors.toList()); } finally { // 恢复原类加载器 Thread.currentThread().setContextClassLoader(originalClassLoader); } } catch (MalformedURLException e) { e.printStackTrace(); return null; } }
3. 确保Spring上下文使用插件类加载器
创建Spring上下文时指定插件类加载器,让Spring能访问插件中的类:
// 加载插件后,创建Spring上下文时传入插件类加载器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.setClassLoader(jarClassLoader); // jarClassLoader为插件的URLClassLoader context.register(PluginConfiguration.class); context.refresh();
验证步骤
- 执行
mvn clean package重新构建插件Fat Jar; - 运行
java -jar simulator.jar,检查是否能正常加载插件并找到GetAuthorizationServiceBean; - 验证Jakarta XML Binding相关功能无缺失依赖错误。
内容的提问来源于stack exchange,提问作者André
相关产品推荐
相关产品推荐

