Gradle构建GeoTools FatJar遇服务类实例化失败问题
问题描述
项目通过FatJar运行,引入GeoTools依赖后,已在shadowJar任务中使用mergeServiceFiles()合并META-INF/services文件,但仍触发以下错误:
WARNING: Can't load a service for category "Operation". Cause is "ServiceConfigurationError: org.geotools.api.coverage.processing.Operation: Provider org.geotools.coverage.processing.operation.ShadedRelief could not be instantiated".
经确认:
org.geotools.api.coverage.processing.Operation(来自gt-api)和org.geotools.coverage.processing.operation.ShadedRelief(来自gt-coverage)均存在于FatJar内- META-INF/services下的对应配置文件已包含该服务提供者条目
- 通过
-classpath直接指定依赖Jar运行时项目正常,仅FatJar模式报错
Gradle配置完全遵循GeoTools官方FatJar指南,使用shadowJar插件,配置如下:
plugins { id("com.gradleup.shadow") version "8.3.0" java } group = "test.it" version = "0.0.1" application { mainClass.set("test.it.ApplicationKt") } repositories { maven { url = uri("https://repo.osgeo.org/repository/release/") } mavenCentral() } dependencies { implementation("org.geotools:gt-coverage:31.2") implementation("org.geotools:gt-main:31.2") implementation("org.geotools:gt-api:31.2") implementation("org.geotools:gt-geotiff:31.2") implementation("org.geotools:gt-tile-client:31.2") implementation("org.geotools.xsd:gt-xsd-kml:31.2") implementation("org.geotools:gt-epsg-hsql:31.2") implementation("org.osgeo:proj4j:0.1.0") } tasks { shadowJar { mergeServiceFiles() isZip64 = true archiveFileName.set("${project.name}-${project.version}.jar") manifest { attributes["Main-Class"] = "test.it.ApplicationKt" // 修复imageio vendor!=null错误 attributes["Specification-Title"] = "Java Advanced Imaging Image I/O Tools" attributes["Specification-Version"] = version attributes["Specification-Vendor"] = "Sun Microsystems, Inc." attributes["Implementation-Title"] = "com.sun.media.imageio" attributes["Implementation-Version"] = version attributes["Implementation-Vendor"] = "Sun Microsystems, Inc." } } }
已尝试但无效的方案:
- 切换GeoTools版本20.0~31.2
- 切换Java版本11/17/21
- 更新Gradle至8.10
临时解决方案:在shadowJar任务中添加exclude("META-INF/services/org.geotools.api.coverage.processing.*")可规避该错误,但需更根本的解决办法。
根本原因分析
问题核心是FatJar构建过程中出现服务配置冲突或类加载异常,具体可能包括:
mergeServiceFiles()合并时,不同依赖的服务配置文件存在重复条目,导致ServiceLoader加载实例时异常- Shaded过程中GeoTools内部依赖类被重命名或类加载顺序异常,导致ShadedRelief实例化时无法找到依赖类
- 合并后的META-INF/services文件存在格式错误(如多余空格、换行),导致ServiceLoader无法正确解析
根本解决办法
方法1:指定服务文件去重合并策略
修改shadowJar任务,针对GeoTools服务文件添加严格去重规则,确保每个服务提供者仅保留一个有效条目:
tasks { shadowJar { mergeServiceFiles() // 针对META-INF/services文件配置去重策略 transform(com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer) { setPath("META-INF/services") // 排除重复条目,保留最后出现的实例(可根据需求调整为保留第一个) duplicatesStrategy = DuplicatesStrategy.EXCLUDE } isZip64 = true archiveFileName.set("${project.name}-${project.version}.jar") manifest { attributes["Main-Class"] = "test.it.ApplicationKt" attributes["Specification-Title"] = "Java Advanced Imaging Image I/O Tools" attributes["Specification-Version"] = version attributes["Specification-Vendor"] = "Sun Microsystems, Inc." attributes["Implementation-Title"] = "com.sun.media.imageio" attributes["Implementation-Version"] = version attributes["Implementation-Vendor"] = "Sun Microsystems, Inc." } } }
方法2:显式指定GeoTools类加载优先级
在manifest中添加类路径配置,确保GeoTools核心依赖优先加载:
tasks { shadowJar { mergeServiceFiles() isZip64 = true archiveFileName.set("${project.name}-${project.version}.jar") manifest { attributes["Main-Class"] = "test.it.ApplicationKt" attributes["Specification-Title"] = "Java Advanced Imaging Image I/O Tools" attributes["Specification-Version"] = version attributes["Specification-Vendor"] = "Sun Microsystems, Inc." attributes["Implementation-Title"] = "com.sun.media.imageio" attributes["Implementation-Version"] = version attributes["Implementation-Vendor"] = "Sun Microsystems, Inc." // 配置类路径优先级,确保GeoTools核心包优先加载 attributes["Class-Path"] = configurations.runtimeClasspath.files.join(" ") } } }
方法3:排查并排除冲突依赖
检查项目依赖树,找出与GeoTools冲突的重复依赖(如重复的gt-api)并排除:
dependencies { implementation("org.geotools:gt-coverage:31.2") { exclude group: 'org.geotools', module: 'gt-api' // 若存在重复依赖则排除 } // 其他依赖配置... }
方法4:Relocate重定位GeoTools包
若为类名冲突导致加载异常,可在shadowJar中重定位GeoTools包:
tasks { shadowJar { mergeServiceFiles() // 重定位GeoTools包,避免与其他依赖类名冲突 relocate 'org.geotools', 'your.project.shaded.org.geotools' isZip64 = true archiveFileName.set("${project.name}-${project.version}.jar") manifest { // 保持原manifest配置 } } }
验证方案
修改配置后重新构建FatJar,运行时添加-Djava.util.logging.config.file=logging.properties启用调试日志,查看ServiceLoader加载日志,确认服务类是否正常实例化。
内容的提问来源于stack exchange,提问作者Lukasz Klosinski

