You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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时,依赖无法被识别:
    1. Spring(非Spring Boot)ComponentScan找不到对应Bean,报错:No qualifying bean of type 'se.plugin.auth.integration.example.authservice.GetAuthorizationService' available;
    2. 手动声明Bean后,出现Jakarta XML Binding-API缺失错误:Implementation of Jakarta XML Binding-API has not been found on module path or classpath;
  • 临时可行的启动方式是java -cp simulator.jar;auth-plugin-with-dependencies.jar Main,但单独运行java -jar simulator.jar时问题复现。

问题根源

  1. Shade插件配置缺陷:当前配置未合并Spring的META-INF/spring.handlers、META-INF/spring.schemas等资源,也未处理ServiceLoader的META-INF/services文件合并,导致Spring无法扫描Bean、ServiceLoader无法正确加载实现类;
  2. 类加载器隔离问题:使用URLClassLoader加载插件Jar时,Spring上下文类加载器仍为主程序类加载器,无法访问插件Jar中的依赖类;
  3. 依赖排除错误: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();

验证步骤

  1. 执行mvn clean package重新构建插件Fat Jar;
  2. 运行java -jar simulator.jar,检查是否能正常加载插件并找到GetAuthorizationService Bean;
  3. 验证Jakarta XML Binding相关功能无缺失依赖错误。

内容的提问来源于stack exchange,提问作者André

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:25:30