java.nio.file.Path для ресурса classpath

143

Есть ли API для получения ресурса classpath (например, из чего я получу Class.getResource(String)) как java.nio.file.Path? В идеале я хотел бы использовать новые PathAPI с ресурсами classpath.

Луи Вассерман
источник
3
Ну, если взять длинный путь (каламбур), у вас есть Paths.get(URI), затем ´URL.toURI () , and last getResource () `, который возвращает a URL. Возможно, вы сможете связать их вместе. Хотя не пробовал.
NilsH

Ответы:

174

Этот работает для меня:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
keyoxy
источник
7
@VGR, если ресурсы в .jar-файле могут попробовать это, `Resource resource = new ClassPathResource (" creation.txt "); BufferedReader reader = new BufferedReader (новый InputStreamReader (resource.getInputStream ())); `пожалуйста, смотрите stackoverflow.com/questions/25869428/…
zhuguowei
8
@zhuguowei это подход, ориентированный на Spring. Это не работает вообще, когда Spring не используется.
Райан Дж. Макдоно
2
Если ваше приложение не использует системный загрузчик классов, оно должно бытьThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA
27

Предположение, что вы хотите сделать, это вызвать Files.lines (...) на ресурсе, который приходит из classpath - возможно, внутри jar.

Так как Oracle запутал понятие, когда Path - это Path, не заставляя getResource возвращать пригодный путь, если он находится в jar-файле, вам нужно сделать что-то вроде этого:

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
user2163960
источник
1
нужен ли предыдущий «/» в вашем случае, я не знаю, но в моем случае class.getResourceтребуется косая черта, но getSystemResourceAsStreamне удается найти файл, если в качестве префикса используется косая черта.
Адам
11

Наиболее общее решение заключается в следующем:

interface IOConsumer<T> {
    void accept(T t) throws IOException;
}
public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException {
    try {
        Path p=Paths.get(uri);
        action.accept(p);
    }
    catch(FileSystemNotFoundException ex) {
        try(FileSystem fs = FileSystems.newFileSystem(
                uri, Collections.<String,Object>emptyMap())) {
            Path p = fs.provider().getPath(uri);
            action.accept(p);
        }
    }
}

Основное препятствие состоит в том, чтобы иметь дело с двумя возможностями: либо иметь существующую файловую систему, которую мы должны использовать, но не закрывать (как с fileURI или хранилищем модуля Java 9), либо иметь необходимость открывать и таким образом безопасно закрывать файловую систему (например, zip / jar файлы).

Таким образом, решение, приведенное выше, инкапсулирует фактическое действие в interface, обрабатывает оба случая, безопасно закрывая его во втором случае, и работает с Java 7 на Java 10. Перед тем, как открыть новую, она проверяет, существует ли уже открытая файловая система, поэтому также работает в том случае, если другой компонент вашего приложения уже открыл файловую систему для того же файла zip / jar.

Он может использоваться во всех версиях Java, указанных выше, например, для перечисления содержимого пакета ( java.langв примере) как Paths, например:

processRessource(Object.class.getResource("Object.class").toURI(), new IOConsumer<Path>() {
    public void accept(Path path) throws IOException {
        try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) {
            for(Path p: ds)
                System.out.println(p);
        }
    }
});

В Java 8 или новее вы можете использовать лямбда-выражения или ссылки на методы для представления фактического действия, например

processRessource(Object.class.getResource("Object.class").toURI(), path -> {
    try(Stream<Path> stream = Files.list(path.getParent())) {
        stream.forEach(System.out::println);
    }
});

сделать то же самое.


Финальная версия модульной системы Java 9 нарушила приведенный выше пример кода. JRE непоследовательно возвращает путь, /java.base/java/lang/Object.classв Object.class.getResource("Object.class")то время как это должно быть /modules/java.base/java/lang/Object.class. Это можно исправить, добавив пропущенный, /modules/если родительский путь указан как несуществующий:

processRessource(Object.class.getResource("Object.class").toURI(), path -> {
    Path p = path.getParent();
    if(!Files.exists(p))
        p = p.resolve("/modules").resolve(p.getRoot().relativize(p));
    try(Stream<Path> stream = Files.list(p)) {
        stream.forEach(System.out::println);
    }
});

Затем он снова будет работать со всеми версиями и методами хранения.

Holger
источник
1
Это решение прекрасно работает! Я могу подтвердить, что это работает со всеми ресурсами (файлами, каталогами) в каталогах classpath и jar classpath. Это определенно то, как следует копировать много ресурсов в Java 7+.
Митчелл Скаггс
10

Оказывается, вы можете сделать это с помощью встроенного поставщика Zip File System . Однако передача ресурса URI напрямую в Paths.getне будет работать; вместо этого сначала необходимо создать файловую систему zip для URI jar без имени записи, а затем обратиться к записи в этой файловой системе:

static Path resourceToPath(URL resource)
throws IOException,
       URISyntaxException {

    Objects.requireNonNull(resource, "Resource URL cannot be null");
    URI uri = resource.toURI();

    String scheme = uri.getScheme();
    if (scheme.equals("file")) {
        return Paths.get(uri);
    }

    if (!scheme.equals("jar")) {
        throw new IllegalArgumentException("Cannot convert to Path: " + uri);
    }

    String s = uri.toString();
    int separator = s.indexOf("!/");
    String entryName = s.substring(separator + 2);
    URI fileURI = URI.create(s.substring(0, separator));

    FileSystem fs = FileSystems.newFileSystem(fileURI,
        Collections.<String, Object>emptyMap());
    return fs.getPath(entryName);
}

Обновить:

Было правильно отмечено, что приведенный выше код содержит утечку ресурсов, поскольку код открывает новый объект FileSystem, но никогда не закрывает его. Наилучший подход - передать объект, похожий на Consumer, так же, как это делает ответ Хольгера. Откройте файловую систему ZipFS достаточно долго, чтобы работник мог делать все, что ему нужно, с Path (если работник не пытается сохранить объект Path для последующего использования), затем закройте FileSystem.

VGR
источник
11
Будьте осторожны с вновь созданными фс. Второй вызов с использованием того же самого jar вызовет исключение с жалобой на уже существующую файловую систему. Будет лучше сделать try (FileSystem fs = ...) {return fs.getPath (entryName);} или если вы хотите, чтобы этот кешированный файл выполнял более сложную обработку. В нынешнем виде это рискованно.
Raisercostin
3
Помимо проблемы потенциально незамкнутой новой файловой системы, предположения о взаимосвязи между схемами и необходимостью открытия новой файловой системы и загадки с содержимым URI ограничивают полезность решения. Я установил новый ответ, который показывает общий подход, который упрощает работу и одновременно обрабатывает новые схемы, такие как хранилище классов Java 9. Это также работает, когда кто-то еще в приложении уже открыл файловую систему (или метод вызывается дважды для одной и той же банки)…
Хольгер
В зависимости от использования этого решения newFileSystemнезакрытое может привести к тому, что несколько ресурсов навсегда останутся открытыми. Хотя @raisercostin addendum избегает ошибки при попытке создать уже созданную файловую систему, если вы попытаетесь использовать возвращаемую, Pathвы получите a ClosedFileSystemException. Ответ @Holger хорошо работает для меня.
Хосе Андиас
Я бы не закрыл FileSystem. Если вы загружаете ресурс из Jar-файла и затем создаете необходимый FileSystem- FileSystemон также позволит вам загружать другие ресурсы из того же Jar-файла. Кроме того, после того, как вы создали новый, FileSystemвы можете просто попытаться загрузить ресурс снова, используя, Paths.get(Path)и реализация автоматически использует новый FileSystem.
NS du Toit
Т.е. вам не нужно использовать #getPath(String)метод на FileSystemобъекте.
NS du Toit
5

Я написал небольшой вспомогательный метод для чтения Pathsиз ресурсов вашего класса. Его очень удобно использовать, так как для этого нужна только ссылка на класс, в котором вы сохранили свои ресурсы, а также имя самого ресурса.

public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
    URL url = resourceClass.getResource(resourceName);
    return Paths.get(url.toURI());
}  
Майкл Штауффер
источник
1

Вы не можете создать URI из ресурсов внутри файла JAR. Вы можете просто записать его во временный файл и затем использовать его (java8):

Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
user1079877
источник
1

Чтение файла из папки ресурсов с помощью NIO, в java8

public static String read(String fileName) {

        Path path;
        StringBuilder data = new StringBuilder();
        Stream<String> lines = null;
        try {
            path = Paths.get(Thread.currentThread().getContextClassLoader().getResource(fileName).toURI());
            lines = Files.lines(path);
        } catch (URISyntaxException | IOException e) {
            logger.error("Error in reading propertied file " + e);
            throw new RuntimeException(e);
        }

        lines.forEach(line -> data.append(line));
        lines.close();
        return data.toString();
    }
Настоящий кодер
источник
0

Вам необходимо определить файловую систему для чтения ресурса из файла jar, как указано в https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . Я успешно прочитал ресурс из jar-файла с кодами ниже:

Map<String, Object> env = new HashMap<>();
try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {

        Path path = fs.getPath("/path/myResource");

        try (Stream<String> lines = Files.lines(path)) {
            ....
        }
    }
user3050291
источник