2013-03-29 3 views
88

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

+3

Ну, взяв длинный путь (каламбур), то есть 'Paths.get (URI)', то'URL.toURI() 'и last' getResource() ', который возвращает' URL'. Возможно, вы сможете объединить их. Не пробовал, хотя. – NilsH

ответ

95

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

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI()); 
+32

Это не будет работать с ресурсами в файлах .jar. – VGR

+3

@VGR, если ресурсы в файле .jar могут попробовать это '\t \t \t Ресурс ресурса = новый ClassPathResource (" usage.txt "); \t \t \t BufferedReader reader = new BufferedReader (новый InputStreamReader (resource.getInputStream())); 'см. Http://stackoverflow.com/questions/25869428/classpath-resource-not-found-when-running-as- jar – zhuguowei

8

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

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); 
} 
+8

Будьте внимательны к вновь созданным ф. Второй вызов с использованием одного и того же баннера вызовет исключение, жалующееся на уже существующую файловую систему. Лучше попробовать try (FileSystem fs = ...) {return fs.getPath (entryName);} или если вы хотите, чтобы этот кешированный файл делал более совершенную обработку. В текущей форме это рискованно. – raisercostin

+1

Помимо проблемы потенциально незамкнутой новой файловой системы, предположения о связи между схемами и необходимостью открытия новой файловой системы и недоумение с содержимым URI ограничивают полезность решения. Я установил [новый ответ] (http://stackoverflow.com/a/36021165/2711488), в котором показан общий подход, который упрощает работу и одновременно обрабатывает новые схемы, такие как новое хранилище классов Java 9. Он также работает, когда кто-то еще в приложении уже открыл файловую систему (или метод вызывается дважды для одного и того же баннера) ... – Holger

+0

В зависимости от использования этого решения не закрытая 'newFileSystem' может привести к разворачиванию нескольких ресурсов навсегда. Хотя добавление @raisercostin позволяет избежать ошибки при попытке создать уже созданную файловую систему, если вы попытаетесь использовать возвращенный 'Path', вы получите' ClosedFileSystemException'. Ответ @Holger хорошо работает для меня. –

3

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

public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException { 
    URL url = resourceClass.getResource(resourceName); 
    return Paths.get(url.toURI()); 
} 
15

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

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

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines(); 
+1

ли предыдущий «/» нужен в вашем случае, я не знаю, но в моем случае 'class.getResource' требует косой черты, но' getSystemResourceAsStream' не может найти файл, если префикс косой черты. – Adam

0

You необходимо определить файловую систему для чтения ресурсов из файла 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)) { 
      .... 
     } 
    } 
7

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

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); 
     } 
    } 
} 

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

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

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

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/AbstractMethodError.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); 
    } 
}); 

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

1

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

Path path = File.createTempFile("some", "address").toPath(); 
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);