Подтвердить что ты не робот

Где файлы ресурсов входят в проект Gradle, который создает модуль Java 9?

Начиная с IDEA 2018.2.1, среда IDE запускает пакеты выделения ошибок "не в графе модулей" из зависимостей, которые были модульными. Я добавил файл module-info.java в свой проект и добавил требуемые requires, но у меня теперь возникают проблемы с доступом к файлам ресурсов в каталоге src/main/resources.

(Полный пример см. В этом проекте GitHub.)

Когда я использовал ./gradlew run или ./gradlew installDist +, полученный в результате сценарий оболочки, я смог прочитать файлы ресурсов, но когда я запускал свое приложение из IDE, я не был.

Я зарегистрировал проблему с JetBrains, и я узнал, что IDEA использует путь к модулю, а Gradle по умолчанию использует путь к классам. Добавив следующий блок в мой build.gradle, я смог заставить Gradle также... не иметь возможности читать файлы ресурсов.

run {
    inputs.property("moduleName", moduleName)
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--module', "$moduleName/$mainClassName"
        ]
        classpath = files()
    }
}

Я попытался export -ing каталог ресурсов, который меня интересовал как "пакет", а во время компиляции получил сбой сборки:

Ошибка: пакет пуст или не существует: mydir

Использование opens вместо exports получило ту же ошибку, хотя и было понижено до предупреждения.

Я даже попытался переместить mydir ресурсов mydir в src/main/java, но это mydir те же ошибки/предупреждения, а также привело к тому, что ресурсы не были скопированы в каталог build.

Где ресурсы, которые предполагается использовать в Java 9, и как мне обращаться к ним?


Примечание. Я значительно изменил этот вопрос, продолжая исследовать проблему. В начальном вопросе я также пытался выяснить, как перечислять файлы в каталоге ресурсов, но в ходе расследования я определил, что это была красная сельдь - во-первых, потому что чтение каталогов ресурсов работает только тогда, когда ресурс будучи прочитанным из file:/// URL (и, возможно, даже не тогда), а во-вторых, поскольку обычные файлы также не работали, так что проблема связана с файлами ресурсов вообще, а не с каталогами.


Решение:

Ответ на build.gradle, я добавил следующее для build.gradle:

// at compile time, put resources in same directories as classes
sourceSets {
  main.output.resourcesDir = main.java.outputDir
}

// at compile time, include resources in module
compileJava {
  inputs.property("moduleName", moduleName)
  doFirst {
    options.compilerArgs = [
      '--module-path', classpath.asPath,
      '--patch-module', "$moduleName=" 
        + files(sourceSets.main.resources.srcDirs).asPath,
      '--module-version', "$moduleVersion"
    ]
    classpath = files()
  }
}

// at run time, make Gradle use the module path
run {
  inputs.property("moduleName", moduleName)
  doFirst {
    jvmArgs = [
      '--module-path', classpath.asPath,
      '--module', "$moduleName/$mainClassName"
    ]
    classpath = files()
  }
}

Замечание: Интересно, что если я не буду продолжать добавлять код Slaw, который заставляет задачу run выполнить JAR, попытка чтения каталога ресурсов InputStream в задаче запуска теперь бросает IOException вместо предоставления списка файлов. (Против JAR он просто получает пустой InputStream.)

4b9b3361

Ответ 1

Из Gradle Epic о поддержке Jigsaw я узнал о плагине, который может облегчить процесс, описанный ниже: gradle-modules-plugin. В эпосе также упоминаются другие плагины, такие как цепная пила (которая является вилкой плагина экспериментальной головоломки). К сожалению, я еще не пробовал ни одного из них, поэтому я не могу прокомментировать, насколько хорошо они обрабатывают ресурсы, если вообще. Я рекомендую попробовать плагин gradle-modules-plugin, похоже, он справляется с По крайней мере, основные конфигурации, необходимые для безболезненного использования модулей Jigsaw. Тем не менее, насколько я могу судить, первоклассная поддержка модулей Jigsaw все еще отсутствует в Gradle 5.5.1.


В вашей стране вы запрашиваете официальную документацию о "правильном способе" обработки ресурсов с помощью модулей Gradle и Jigsaw. Ответ, насколько мне известно, заключается в том, что "правильного пути" не существует, потому что Gradle до сих пор (начиная с 4.10-rc-2) не имеет первоклассной поддержки для модулей Jigsaw. Самый близкий к вам документ Сборка модулей Java 9.

Однако вы упоминаете, что речь идет о доступе к ресурсам из модуля (т.е. не из внешних модулей). Это не должно быть слишком сложно исправить с помощью простой конфигурации build.gradle.

По умолчанию Gradle разделяет выходные каталоги для классов и ресурсов. Это выглядит примерно так:

build/
|--classes/
|--resources/

При использовании задачи run значение classpath равно значению sourceSets.main.runtimeClasspath. Это значение включает в себя оба каталога, и это работает из-за способа работы classpath. Вы можете думать об этом, как будто classpath - это всего лишь один гигантский модуль.

Это не работает, однако, при использовании пути к модулю, потому что технически файлы внутри resources не принадлежат модулю, который находится внутри classes. Нам нужен способ сообщить модульной системе, что resources является частью модуля. К счастью, есть --patch-module. Эта опция будет (цитата из java --help-extra):

переопределить или дополнить модуль классами и ресурсами в JAR файлах или каталогах.

И имеет следующий формат (я предполагаю, что разделитель ; зависит от платформы):

--patch-module <module>=<file>(;<file>)*

Чтобы разрешить вашему модулю доступ к собственным ресурсам, просто настройте задачу run следующим образом:

run {
    input.property('moduleName', moduleName)
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--patch-module', "$moduleName=" + files(sourceSets.main.output.resourcesDir).asPath,
                '--module', "$moduleName/$mainClassName"
        ]
        classpath = files()
    }
}

Вот как я это делал, и до сих пор это получалось довольно хорошо.


Но как вы разрешаете внешним модулям получать доступ к ресурсам вашего модуля при запуске приложения из Gradle? Это становится немного более сложным.

Если вы хотите разрешить внешним модулям доступ к ресурсам, ваш модуль должен opens (см. Eng.Fouad answer) пакет ресурсов по крайней мере для модуля чтения (это относится только к инкапсулированному Ресурсы). Однако, как вы обнаружили, это приводит к предупреждению компиляции и ошибкам во время выполнения.

  1. Предупреждение о компиляции связано с тем, что вы пытаетесь opens создать пакет, который не существует в соответствии с системой модулей.
    • Этого следует ожидать, поскольку каталог ресурсов не включается при компиляции по умолчанию (при условии, что пакет только для ресурсов).
  2. Ошибка во время выполнения состоит в том, что система модулей не может найти пакет, для которого вы объявили директиву opens.
    • Опять же, при условии пакета только для ресурсов.
    • Это происходит даже с упомянутой выше опцией --patch-module. Я предполагаю, что система модулей выполняет некоторую проверку целостности перед применением исправления.

Примечание. Под "только ресурсами" я подразумеваю пакеты, в которых нет файлов .java/.class.

Чтобы исправить предупреждение о компиляции, вам просто нужно снова использовать --patch-module внутри задачи compileJava. На этот раз вы будете использовать исходные каталоги ресурсов, а не выходной каталог.

compileJava {
    inputs.property('moduleName', moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--patch-module', "$moduleName=" + files(sourceSets.main.resources.srcDirs).asPath,
                '--module-version', "$version"
        ]
    }
}

Для ошибки во время выполнения есть несколько вариантов. Первый вариант - объединить выходной каталог ресурсов с выходным каталогом для классов.

sourceSets {
    main.output.resourcesDir = main.java.outputDir
}

jar {
    // I've had bad experiences when "merging" output directories
    // where the jar task ends up creating duplicate entries in the JAR.
    // Use this option to combat that.
    duplicateStrategy = DuplicatesStrategy.EXCLUDE
}

Второй вариант - настроить задачу run для выполнения файла JAR, а не разнесенного каталога (ов). Это работает, потому что, как и первый вариант, он объединяет классы и ресурсы в одном месте и, следовательно, ресурсы являются частью модуля.

run {
    dependsOn += jar
    inputs.property('moduleName', moduleName)
    doFirst {
        // add JAR file and runtime classpath. The runtime classpath includes the output of the main source set
        // so we remove that to avoid having two of the same module on the modulepath
        def modulepath = files(jar.archivePath) + (sourceSets.main.runtimeClasspath - sourceSets.main.output)
        jvmArgs = [
                '--module-path', modulepath.asPath,
                '--module', "$moduleName/$mainClassName"
        ]
        classpath = files()
    }
}

Обе эти опции можно использовать вместо использования --patch-module в задаче run (объяснено в первой части этого ответа).


В качестве бонуса, вот как я добавляю атрибут --main-class в мои модульные файлы JAR:

jar {
    inputs.property('mainClassName', mainClassName)
    doLast {
        exec {
            executable = 'jar'
            args = [
                    '--update', '--file', "$archivePath",
                    '--main-class', "$mainClassName"
            ]
        }
    }
}

Это позволяет вам использовать java -m module.name вместо java -m module.name/some.package.Main. Кроме того, если задача run настроена на выполнение JAR, вы можете изменить:

'--module', "$moduleName/$mainClassName"

To:

'--module', "$moduleName"

P.S. Если есть лучший способ сделать это, пожалуйста, дайте мне знать.

Ответ 2

Кроме ответа @Slaw (спасибо ему), мне пришлось открыть пакет, содержащий ресурсы для модуля вызывающей стороны. Следующим образом (moduleone.name module-info.java):

opens io.fouad.packageone to moduletwo.name;

В противном случае следующее будет возвращено null:

A.class.getResource("/io/fouad/packageone/logging.properties");

учитывая, что класс A находится в модуле moduletwo.name, а файл logging.properties находится внутри модуля moduleone.name.


В качестве альтернативы, moduleone.name может предоставить служебный метод, который возвращает ресурс:

public static URL getLoggingConfigFileAsResource()
{
    return A.class.getResource("/io/fouad/packageone/logging.properties");
}