Недавно я готовил средство проверки соединения для мощной среды удаленного выполнения Deployit Overthere . Чтобы сделать чекер настолько компактным, насколько это возможно, я собрал флягу 1 для раздачи.
Тесты и пробные запуски из IDE работали, поэтому я ожидал, что пробный запуск дистрибутива будет быстрой формальностью. Вместо этого: бум!
Оказывается, что одна из библиотек, используемых Overthere, TrueZIP — или вообще любой код, который использует механизм SPI Java 2 — не подходит для идеи jar-with-deps .
Видя двойной
TrueZIP использует файл конфигурации провайдера de.schlichtherle.truezip.fs.spi.FsDriverService в META-INF / services для регистрации драйверов для различных форматов архивов, и проверка завершалась сбоем, поскольку один из необходимых драйверов не загружался, хотя код был правильно включен в jar-with-deps .
Опция дублирования задачи 3 Jar Ant и выпуска 1050 Gradle , в которой говорится о слиянии файлов при создании архива, намекает на проблему: после упаковки в один архив jar-with-deps только один из файлов конфигурации провайдера TrueZIP был быть найденным
Maven, Gradle и Java
После того, как я рассмотрел jar-with-deps, созданный Maven 4 , стало ясно, почему: JAR содержит только один из файлов конфигурации — выглядит как первый, встречающийся в моем случае, но это может быть недетерминированным. Предположительно, это происходит потому, что Maven использует временный каталог для подготовки содержимого архива, который, конечно, не может содержать несколько файлов с одинаковыми именами.
С Gradle 5 все становится немного интереснее, потому что архив действительно содержит все файлы конфигурации — формат ZIP поддерживает повторяющиеся записи 6 . Однако ClassLoader.getResources не подыгрывает, поэтому снова обнаруживается только одна запись.
Может быть только один
По сути, кажется, что объединение затронутых файлов — единственный реальный способ обойти это. Вот возможный фрагмент Gradle 7 :
task jarWithDeps(type: Jar, dependsOn: classes) { mergeDir = "${buildDir}/merge" // might need a lazy var in a multi-module project where deps are inherited runtimeDeps = configurations.runtime.collect { zipTree(it) } doFirst { new File(mergeDir).delete() mergeFiles(mergeDir, runtimeDeps, file-to-merge) // ... possibly other files too } // this project's classes and all deps from sourceSets*.classesDir from(runtimeDeps) { exclude file-to-merge } from mergeDir } private def mergeFiles(targetDir, fileTrees, relativePath) { // prepare the merge mergedFile = new File("${targetDir}/${relativePath}") new File(mergedFile.parent).mkdirs() fileTrees*.matching({ include "**/${relativePath}" })*.each { mergedFile << it.bytes } }
К сожалению, я не смог найти подобную опцию для архиватора Maven , но я думаю, что в экосистеме Maven должно быть что-то там
- Ака «FarJar». Эквивалентное определение задачи Gradle см. Здесь .
- Посмотрите MultiSPI для подхода, который обеспечивает большую гибкость и «современные альтернативы» для поставщиков услуг.
- Обратите внимание , что баночка утилита сама не обеспечивает такой же вариант.
- запустить mvn clean package
- запусти gradle clean jarWithDeps
- хотя ZipOutputStream явно не был ими доволен и, судя по реализации 1.6.0_20, все равно будет выдавать исключение ZipException
- Было бы интересно попытаться выполнить слияние во время создания JAR, возможно, сохранив «merge-so-far (s)» в mergeDir и используя eachFile для замены неопрятных файлов и обновив «merge-so-far (s)» » в то же время.
С http://blog.xebia.com/2011/07/jar-with-deps-dont-like-meta-infservices/