Доступ к файлам ресурсов из внешних модулей

До тех пор, пока не появилась немодульная Java, вы просто поместите файл в src/main/java/resources, убедитесь, что он находится в пути к классам, а затем загрузите его с помощью

file = getClass().getClassLoader().getResourceAsStream("myfilename"); 

практически из любого места в пути к классам.

Теперь с модулями сюжет утолщается.

Настройка моего проекта следующая:

module playground.api {
    requires java.base;
    requires java.logging;
    requires framework.core;
}

Файл конфигурации находится внутри src/main/resources/config.yml.

Проект запускается с

java -p target/classes:target/dependency -m framework.core/com.framework.Main

Поскольку основной класс находится не в моем собственном проекте, а во внешнем модуле фреймворка, он не может видеть config.yml. Теперь вопрос в том, есть ли способ как-то поместить мой файл конфигурации в модуль или открыть его? Должен ли я изменить способ загрузки файла вышестоящим фреймворком?

Я пробовал использовать «экспорт» или «открывает» в информации о модуле, но он хочет иметь имя пакета, а не имя папки.

Как добиться этого наилучшим практическим способом, чтобы он работал как в Java 8 и с минимальными изменениями?


person cen    schedule 21.10.2017    source источник
comment
com.framework.Main читает ресурсы с помощью Class.getResource?   -  person Naman    schedule 21.10.2017
comment
Если коду в модуле требуется доступ к одному из его собственных ресурсов, следует использовать методы Class.getResourceXXX (имя параметра - это имя ресурса, а не имя файла, кстати). Если ресурс находится в другом модуле и у вас есть объект Module, вы можете использовать Module.getResourceAsStream. Если вы хотите найти путь к модулю и путь к классу, ClassLoader.getResourceXXX будет работать, как и раньше, но модулю необходимо открыть пакет, содержащий ресурс. Ресурсы в самом верхнем каталоге или в META-INF / * не инкапсулируются, поэтому ClassLoader.getResource будет работать.   -  person Alan Bateman    schedule 21.10.2017
comment
Связанные инструкции -an-automatic-module-find-its-own-resources-in-java-9   -  person Naman    schedule 22.10.2017
comment
@nullpointer да, это так   -  person cen    schedule 22.10.2017
comment
@Alan Bateman, как мне открыть файлы ресурсов, особенно src/main/resources, если это не пакет? Думаю, я мог бы положить его в пакет, но это похоже на антипаттерн. Это первая часть моего замешательства. Во-вторых, под верхним уровнем вы подразумеваете, какую папку? Я также попытался поместить свой файл конфигурации в META-INF, но без кубиков.   -  person cen    schedule 22.10.2017
comment
@cen Я пробовал использовать экспорт или открытие в информации о модуле ... не могли бы вы поделиться тем, что вы пробовали, но не сработали.   -  person Naman    schedule 22.10.2017
comment
Я просто наивно пытался opens src/main/resources, офс не компилируется.   -  person cen    schedule 22.10.2017
comment
src / main / resources / config.yml - это путь к файлу в дереве src, а не имя ресурса. Посмотрите в файле JAR, находится ли config.yml в каталоге верхнего уровня файла JAR? Если это так, то фреймворк, как и раньше, найдет его с помощью ClassLoader.getResourceXXX.   -  person Alan Bateman    schedule 22.10.2017
comment
@Alan Bateman, он внутри банки, тоже в target/classes, но не работает. Это сработает, если я добавлю -cp target/classes. Но нужно ли использовать -cp при работе с лобзиком? По какой-то причине я не должен этого делать.   -  person cen    schedule 22.10.2017
comment
Правильно, вам не следует добавлять классы модулей в путь к классам с помощью -cp. Можете ли вы вместо этого добавить --add-moduels playground.api в командную строку. Первоначальный модуль (модуль, который вы указываете для -m) - com.framework.Main, и я предполагаю, что никому не требуется игровая площадка .api, поэтому он не разрешается (вы можете быстро проверить это, добавив --show-module-resolution для разрешения трассировки при запуске).   -  person Alan Bateman    schedule 22.10.2017
comment
Бинго! В этом есть смысл.   -  person cen    schedule 22.10.2017
comment
Это может быть полезно: stackoverflow.com/a/53184054/597657   -  person Eng.Fouad    schedule 07.11.2018


Ответы (3)


Пока вы используете команду java для запуска приложения следующим образом : -

java -p target/classes:target/dependency -m framework.core/com.framework.Main 
  • вы указываете путь к модулю, используя параметр -p aternate для --module-path, который будет искать в target / classes и target / dependency для ваших модулей.

  • Наряду с этим, использование -m alternate для --module указывает начальный модуль для разрешения с именем framework.core и создает граф модуля с основным классом для выполнения, явно указанным как com.framework.Main.

Проблема заключается в том, что модуль framework.core не requires и не читает playground.api модуль, из-за чего график модуля не включает желаемый модуль, состоящий из фактического ресурса config.yml.

Как предложено @Alan, Хороший способ отобразить вывод разрешения модуля во время запуска - использовать параметр --show-module-resolution.


Я просто наивно пытался открыть src / main / resources, не компилирует ofc

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

В вашем случае вам просто нужно убедиться, что модуль playground.api попадает в граф модуля, и тогда ресурс будет доступен для приложения. Чтобы указать корневые модули для разрешения в дополнение к начальному модулю, вы можете использовать параметр --add-modules.


Следовательно, общее решение, которое будет работать для вас вместе с некоторой отладкой, будет следующим:

java --module-path target/classes:target/dependency 
     --module framework.core/com.framework.Main
     --add-modules playground.api
     --show-module-resolution
person Naman    schedule 23.10.2017

// to scan the module path
ClassLoader.getSystemResources(resourceName)

// if you know a class where the resource is
Class.forName(className).getResourceAsStream(resourceName)

// if you know the module containing the resource
ModuleLayer.boot().findModule(moduleName).getResourceAsStream(resourceName)

См. Рабочий пример ниже.


Данный:

.
├── FrameworkCore
│   └── src
│       └── FrameworkCore
│           ├── com
│           │   └── framework
│           │       └── Main.java
│           └── module-info.java
└── PlaygroundApi
    └── src
        └── PlaygroundApi
            ├── com
            │  └── playground
            │      └── api
            │          └── App.java
            ├── config.yml
            └── module-info.java

Main.java может быть

package com.framework;

import java.io.*;
import java.net.URL;
import java.util.Optional;
import java.util.stream.Collectors;

public class Main {
    public static void main( String[] args )
    {
        // load from anywhere in the modulepath
        try {
            URL url = ClassLoader.getSystemResources("config.yml").nextElement();
            InputStream is = url.openStream();
            Main.read(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // load from the the module where a given class is
        try {
            InputStream is = Class.forName("com.playground.api.App").getResourceAsStream("/config.yml");
            Main.read(is);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        // load from a specific module
        Optional<Module> specificModule = ModuleLayer.boot().findModule("PlaygroundApi");
        specificModule.ifPresent(module -> {
            try {
                InputStream is = module.getResourceAsStream("config.yml");
                Main.read(is);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    private static void read(InputStream is) {
        String s = new BufferedReader(new InputStreamReader(is)).lines().collect(Collectors.joining("\n"));
        System.out.println("config.yml: " + s);
    }
}

И вы бы запустили с

java --module-path ./FrameworkCore/target/classes:./PlaygroundApi/target/classes \
     --add-modules FrameworkCore,PlaygroundApi \
       com.framework.Main

Чтобы клонировать этот пример: git clone https://github.com/j4n0/SO-46861589.git

person Jano    schedule 22.10.2017
comment
См. Приложение .java: 20 для загрузки ресурса из того же модуля. - person Jano; 22.10.2017
comment
Я попробую это, просто чтобы посмотреть, работает ли это, однако у меня с этим проблема. В идеале модуль фреймворка не должен заботиться о том, какой модуль содержит файл ресурсов, и не должен заботиться о том, чтобы рассматриваемый модуль был назван PlaygroundApi. Если я создам 100 проектов, у каждого из них будет другое имя модуля, что означает, что мне нужно сделать имя модуля в коде фреймворка настраиваемым. Довольно болезненно, но вполне выполнимо. - person cen; 22.10.2017
comment
Ответ немного вводит в заблуждение, так как там фреймворку нужно только использовать API модуля, тогда он хочет найти ресурс в конкретном модуле. Как отмечается в одном из ранних комментариев, ресурс инкапсулируется только в том случае, если он находится в пакете модуля, и в этом случае module-info.java может открыть пакет, чтобы позволить фреймворку найти ресурс. - person Alan Bateman; 22.10.2017
comment
Спасибо. Я разделил код и добавил загрузчик классов для основных ресурсов в любом месте пути к модулю. - person Jano; 22.10.2017
comment
Вводный раздел в вашем ответе сейчас хорош. Я предполагаю, что кто-то должен написать ответ, чтобы резюмировать реальную проблему, поскольку оказалось, что это не проблема инкапсуляции ресурсов. - person Alan Bateman; 23.10.2017

При запуске в именованном модуле ClassLoader#getResource ведет себя очень неожиданно. ClassLoader#getResourceAsStream так же проблематичен. Я сам столкнулся с этим при обновлении приложения до именованных модулей.

Если ваш код находится в названном модуле и вы обращаетесь к ресурсу через ClassLoader#getResource, пакет этого ресурса должен быть открыт безоговорочно. В противном случае вы не сможете получить ресурс, даже если ресурс находится в том же модуле.


Это отличается от поведения Class#getResource - обратите внимание на различие. Class#getResource прост и не имеет этого неприятного сюрприза.

Все сказанное о getResource применимо и к getResourceAsStream методам.

По этой причине я предлагаю всегда использовать методы getResource на Class, а НЕ на ClassLoader.

Также см. Мой ответ на В чем разница между Class.getResource и ClassLoader.getResource

person A248    schedule 04.04.2021