java.nio.file.Path para un recurso classpath

143

¿Hay una API para obtener un recurso classpath (por ejemplo, de qué obtendría Class.getResource(String)) como a java.nio.file.Path? Idealmente, me gustaría usar las nuevas PathAPI elegantes con recursos classpath.

Louis Wasserman
fuente
3
Bueno, tomando el camino largo (juego de palabras previsto), tienes Paths.get(URI), luego ´URL.toURI () , and last getResource () `que devuelve a URL. Es posible que pueda encadenarlos juntos. Aunque no lo he intentado.
NilsH

Respuestas:

174

Esta funciona para mí:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
keyoxy
fuente
77
@VGR si los recursos en el archivo .jar podrían probar este `Resource resource = new ClassPathResource (" use.txt "); BufferedReader reader = new BufferedReader (new InputStreamReader (resource.getInputStream ())); `consulte stackoverflow.com/questions/25869428/…
zhuguowei
8
@zhuguowei ese es un enfoque específico de Spring. No funciona en absoluto cuando no se usa Spring.
Ryan J. McDonough
2
Si su aplicación no se basa en el cargador de clases del sistema, debería serloThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA el
27

Adivinando que lo que quieres hacer es llamar a Files.lines (...) en un recurso que proviene del classpath, posiblemente desde un jar.

Dado que Oracle enreda la noción de cuándo una ruta es una ruta al no hacer que getResource devuelva una ruta utilizable si reside en un archivo jar, lo que debe hacer es algo como esto:

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
usuario2163960
fuente
1
si se necesita la "/" anterior en su caso, no lo sé, pero en mi caso class.getResourcerequiere una barra inclinada, pero getSystemResourceAsStreamno puedo encontrar el archivo cuando está prefijado con una barra inclinada.
Adam
11

La solución más general es la siguiente:

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

El principal obstáculo es lidiar con las dos posibilidades, ya sea, tener un sistema de archivos existente que deberíamos usar, pero no cerrar (como con los fileURI o el almacenamiento del módulo de Java 9), o tener que abrir y cerrar el sistema de archivos nosotros mismos (como archivos zip / jar).

Por lo tanto, la solución anterior encapsula la acción real en un interface, maneja ambos casos, luego se cierra de forma segura en el segundo caso y funciona de Java 7 a Java 10. Prueba si ya hay un sistema de archivos abierto antes de abrir uno nuevo, por lo que también funciona en el caso de que otro componente de su aplicación ya haya abierto un sistema de archivos para el mismo archivo zip / jar.

Se puede usar en todas las versiones de Java mencionadas anteriormente, por ejemplo, para enumerar el contenido de un paquete ( java.langen el ejemplo) como Paths, como este:

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

Con Java 8 o más reciente, puede usar expresiones lambda o referencias de métodos para representar la acción real, p. Ej.

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

hacer lo mismo.


La versión final del sistema de módulos de Java 9 ha roto el ejemplo de código anterior. El JRE devuelve inconsistentemente la ruta /java.base/java/lang/Object.classpor Object.class.getResource("Object.class")donde debería ser /modules/java.base/java/lang/Object.class. Esto se puede solucionar anteponiendo lo que falta /modules/cuando la ruta principal se informa como inexistente:

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

Luego, volverá a funcionar con todas las versiones y métodos de almacenamiento.

Holger
fuente
1
¡Esta solución funciona muy bien! Puedo confirmar que esto funciona con todos los recursos (archivos, directorios) tanto en classpaths de directorio como en classpaths de jar. Esta es definitivamente la forma de copiar muchos recursos en Java 7+.
Mitchell Skaggs
10

Resulta que puedes hacer esto, con la ayuda del proveedor del sistema de archivos Zip incorporado . Sin embargo, pasar un URI de recurso directamente a Paths.getno funcionará; en su lugar, primero debe crear un sistema de archivos zip para el URI jar sin el nombre de la entrada, luego consulte la entrada en ese sistema de archivos:

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

Actualizar:

Se ha señalado correctamente que el código anterior contiene una fuga de recursos, ya que el código abre un nuevo objeto FileSystem pero nunca lo cierra. El mejor enfoque es pasar un objeto de trabajo similar al consumidor, al igual que la respuesta de Holger. Abra el Sistema de archivos ZipFS el tiempo suficiente para que el trabajador haga lo que tenga que hacer con la Ruta (siempre que el trabajador no intente almacenar el objeto Ruta para su uso posterior), luego cierre el Sistema de archivos.

VGR
fuente
11
Tenga cuidado con los fs recién creados. Una segunda llamada que use el mismo jar arrojará una excepción quejándose de un sistema de archivos ya existente. Será mejor intentarlo (FileSystem fs = ...) {return fs.getPath (entryName);} o si desea tener esto en caché, haga un manejo más avanzado. En la forma actual es arriesgado.
raisercostin
3
Además de la cuestión del nuevo sistema de archivos potencialmente no cerrado, las suposiciones sobre la relación entre los esquemas y la necesidad de abrir un nuevo sistema de archivos y la confusión con el contenido de URI limitan la utilidad de la solución. He configurado una nueva respuesta que muestra un enfoque general que simplifica la operación y maneja nuevos esquemas como el nuevo almacenamiento de clase Java 9 al mismo tiempo. También funciona cuando alguien más dentro de la aplicación ya ha abierto el sistema de archivos (o el método se llama dos veces para el mismo jar) ...
Holger
Dependiendo del uso de esta solución, lo no cerrado newFileSystempuede generar múltiples recursos abiertos para siempre. Aunque el anexo @raisercostin evita el error al intentar crear un sistema de archivos ya creado, si intenta utilizar el devuelto Pathobtendrá un ClosedFileSystemException. La respuesta de @Holger funciona bien para mí.
José Andias
No cerraría el FileSystem. Si se carga un recurso de un tarro, y después crea la requerida FileSystem- el FileSystemtambién le permitirá cargar otros recursos de la misma tarro. Además, una vez que haya creado el nuevo FileSystem, puede intentar cargar el recurso nuevamente usando Paths.get(Path)y la implementación usará automáticamente el nuevo FileSystem.
NS du Toit
Es decir, no tiene que usar el #getPath(String)método en el FileSystemobjeto.
NS du Toit
5

Escribí un pequeño método auxiliar para leer Pathslos recursos de tu clase. Es bastante útil de usar, ya que solo necesita una referencia de la clase en la que ha almacenado sus recursos, así como el nombre del recurso en sí.

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

No puede crear URI a partir de recursos dentro del archivo jar. Simplemente puede escribirlo en el archivo temporal y luego usarlo (java8):

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

Leer un archivo de la carpeta de recursos usando NIO, en 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();
    }
Codificador real
fuente
0

Debe definir el Sistema de archivos para leer el recurso del archivo jar como se menciona en https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . Tengo éxito al leer el recurso del archivo jar con los siguientes códigos:

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)) {
            ....
        }
    }
usuario3050291
fuente