¿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 Path
API elegantes con recursos classpath.
¿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 Path
API elegantes con recursos classpath.
Respuestas:
Esta funciona para mí:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
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();
class.getResource
requiere una barra inclinada, pero getSystemResourceAsStream
no puedo encontrar el archivo cuando está prefijado con una barra inclinada.
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 file
URI 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.lang
en el ejemplo) como Path
s, 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.class
por 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.
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.get
no 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.
newFileSystem
puede 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 Path
obtendrá un ClosedFileSystemException
. La respuesta de @Holger funciona bien para mí.
FileSystem
. Si se carga un recurso de un tarro, y después crea la requerida FileSystem
- el FileSystem
tambié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
.
#getPath(String)
método en el FileSystem
objeto.
Escribí un pequeño método auxiliar para leer Paths
los 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());
}
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);
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();
}
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)) {
....
}
}
Paths.get(URI)
, luego ´URL.toURI (), and last
getResource () `que devuelve aURL
. Es posible que pueda encadenarlos juntos. Aunque no lo he intentado.