多JDBC驱动场景下正确的DuplicatesStrategy应如何选择?
冲突文件覆盖的影响
你遇到的重复文件分为两类,覆盖后的影响各不相同:
META-INF/services/java.sql.Driver是Java SPI(服务提供者接口)机制的注册文件,每个JDBC驱动都会在这个文件里声明自己的驱动实现类全限定名。文件被覆盖后,只有最后打包进jar的驱动会被SPI自动发现,你当前场景下Oracle的驱动文件覆盖了SQL Server的,如果你代码里没有手动通过Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver")加载驱动,使用DriverManager获取SQL Server连接时会抛出「找不到驱动」的异常。META-INF/native-image/下的文件是GraalVM原生镜像的编译配置文件,如果你用GraalVM打原生镜像,这类文件覆盖会导致部分依赖的编译配置缺失,轻则原生镜像运行时报错,重则直接编译失败;如果你只在标准JVM环境运行程序,这类文件覆盖暂时不会产生影响。
普通的业务类、第三方依赖的类文件只有全限定名完全相同时才会被覆盖,这种属于真正的依赖冲突,你当前遇到的都是元数据配置文件冲突,不用过度担忧。
这类冲突的普遍性
这类冲突非常常见,属于fat jar(将所有依赖打进同一个jar包)打包场景的典型问题。只要多个依赖用到了SPI机制、或者自带同名的元数据配置文件,打包时就会出现路径重复的问题,几乎所有多依赖的fat jar打包都会遇到这类问题。
解决方案
可以通过合并配置的方式保留所有文件的有效内容,常用方案有三种:
1. 优先推荐:不打fat jar
不要把所有依赖都打进同一个jar,把第三方依赖统一放在单独的lib目录下,启动时用以下命令启动即可,完全不会出现文件冲突问题:
java -cp 你的主jar包名.jar:lib/* com.example.App
2. 自定义打包逻辑合并冲突文件
如果必须打fat jar,可以修改jar任务的配置,针对不同类型的冲突文件做合并处理,修改后的配置如下:
plugins { id 'java' } repositories { mavenCentral() } dependencies { implementation 'com.oracle.database.jdbc:ojdbc11-production:21.3.0.0' implementation 'com.microsoft.sqlserver:mssql-jdbc:9.4.0.jre8' } tasks.named('jar') { // 先设置重复策略为排除,避免默认覆盖,后续针对需要合并的文件单独处理 setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE) manifest { attributes 'Main-Class': 'com.example.App' } // 合并SPI服务注册文件 def mergedServicesDir = file("${buildDir}/tmp/jar-merged/services") doFirst { mergedServicesDir.deleteDir() mergedServicesDir.mkdirs() // 遍历所有依赖的服务文件,合并内容 configurations.compileClasspath.each { depJar -> if (depJar.file && depJar.name.endsWith(".jar")) { zipTree(depJar).matching { include "META-INF/services/**" }.each { serviceFile -> def targetFile = new File(mergedServicesDir, serviceFile.name) targetFile.append(serviceFile.text + System.lineSeparator()) } } } } from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } // 引入合并后的SPI服务文件,覆盖原有分散的文件 from(mergedServicesDir) { into "META-INF/services" } // 不需要GraalVM原生镜像打包的话可以直接排除该目录下的所有文件,避免冲突 exclude "META-INF/native-image/**" // 如果需要保留GraalVM配置,可以参考SPI文件的合并逻辑,按配置文件的格式写对应的合并规则即可 }
3. 用Shadow插件简化处理
可以直接用Gradle的Shadow插件,内置了常见冲突文件的合并策略,不需要自己写合并逻辑,只需要在配置里开启服务文件、原生镜像配置文件的合并开关即可自动处理所有冲突。
内容的提问来源于stack exchange,提问作者EMEM
相关产品推荐
相关产品推荐

