Esiste un'API per ottenere una risorsa del percorso di classe (ad es. Da cosa otterrei Class.getResource(String)
) come java.nio.file.Path
? Idealmente, mi piacerebbe usare le nuove fantasiose Path
API con risorse per i percorsi di classe.
Esiste un'API per ottenere una risorsa del percorso di classe (ad es. Da cosa otterrei Class.getResource(String)
) come java.nio.file.Path
? Idealmente, mi piacerebbe usare le nuove fantasiose Path
API con risorse per i percorsi di classe.
Risposte:
Questo funziona per me:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
Indovinare che cosa vuoi fare, è chiamare Files.lines (...) su una risorsa che proviene dal percorso di classe - possibilmente da all'interno di un barattolo.
Poiché Oracle ha contorto la nozione di quando un Path è un Path non facendo in modo che getResource restituisca un percorso utilizzabile se risiede in un file jar, quello che devi fare è qualcosa del genere:
Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
class.getResource
richiede una barra ma getSystemResourceAsStream
non riesco a trovare il file quando è preceduto da una barra.
La soluzione più generale è la seguente:
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);
}
}
}
Il principale ostacolo è gestire entrambe le possibilità, avendo un filesystem esistente che dovremmo usare, ma non vicino (come con file
URI o l'archiviazione del modulo Java 9), o dover aprire e quindi chiudere noi stessi il filesystem in modo sicuro (come file zip / jar).
Pertanto, la soluzione sopra incapsula l'azione effettiva in un interface
, gestisce entrambi i casi, chiudendo in modo sicuro nel secondo caso e funziona da Java 7 a Java 10. Verifica se esiste già un filesystem aperto prima di aprirne uno nuovo, quindi funziona anche nel caso in cui un altro componente dell'applicazione abbia già aperto un filesystem per lo stesso file zip / jar.
Può essere utilizzato in tutte le versioni Java sopra citate, ad esempio per elencare il contenuto di un pacchetto ( java.lang
nell'esempio) come Path
s, in questo modo:
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 versioni successive, è possibile utilizzare espressioni lambda o riferimenti a metodi per rappresentare l'azione effettiva, ad es
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
try(Stream<Path> stream = Files.list(path.getParent())) {
stream.forEach(System.out::println);
}
});
fare lo stesso.
La versione finale del sistema di moduli di Java 9 ha rotto l'esempio di codice sopra riportato. JRE restituisce in modo incoerente il percorso /java.base/java/lang/Object.class
per Object.class.getResource("Object.class")
mentre dovrebbe essere /modules/java.base/java/lang/Object.class
. Questo può essere risolto anteponendo ciò che manca /modules/
quando il percorso principale viene segnalato come inesistente:
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);
}
});
Quindi, funzionerà di nuovo con tutte le versioni e i metodi di archiviazione.
Si scopre che è possibile farlo, con l'aiuto del provider di file system Zip integrato . Tuttavia, passare un URI di risorsa direttamente a Paths.get
non funzionerà; invece, si deve prima creare un filesystem zip per l'URI jar senza il nome della voce, quindi fare riferimento alla voce in quel filesystem:
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);
}
Aggiornare:
È stato giustamente sottolineato che il codice sopra contiene una perdita di risorse, poiché il codice apre un nuovo oggetto FileSystem ma non lo chiude mai. L'approccio migliore è passare un oggetto lavoratore simile al consumatore, proprio come fa la risposta di Holger. Aprire il FileSystem ZipFS abbastanza a lungo da consentire al lavoratore di fare tutto ciò che deve fare con il Path (purché il lavoratore non tenti di memorizzare l'oggetto Path per un uso successivo), quindi chiudere il FileSystem.
newFileSystem
può portare a risorse multiple in sospeso per sempre. Sebbene l'addendum @raisercostin eviti l'errore quando si tenta di creare un file system già creato, se si tenta di utilizzare il reso Path
verrà visualizzato un ClosedFileSystemException
. La risposta di @Holger funziona bene per me.
FileSystem
. Se carichi una risorsa da un Jar e poi crei il necessario FileSystem
, ciò FileSystem
ti permetterà anche di caricare altre risorse dallo stesso Jar. Inoltre, una volta creato il nuovo FileSystem
, puoi semplicemente provare a caricare nuovamente la risorsa utilizzando Paths.get(Path)
e l'implementazione utilizzerà automaticamente il nuovo FileSystem
.
#getPath(String)
metodo FileSystem
sull'oggetto.
Ho scritto un piccolo metodo di supporto per leggere Paths
le risorse della tua classe. È abbastanza utile da usare in quanto richiede solo un riferimento alla classe in cui sono state memorizzate le risorse e il nome della risorsa stessa.
public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
URL url = resourceClass.getResource(resourceName);
return Paths.get(url.toURI());
}
Non è possibile creare URI da risorse all'interno del file jar. Puoi semplicemente scriverlo nel file temporaneo e quindi usarlo (java8):
Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
Leggi un file dalla cartella delle risorse utilizzando NIO, in 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();
}
È necessario definire il filesystem per leggere la risorsa dal file jar come indicato in https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . Sono riuscito a leggere la risorsa dal file jar con i codici seguenti:
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)
parole previsto), hai , quindi ´URL.toURI (), and last
getResource () `che restituisce aURL
. Potresti riuscire a metterli insieme. Non ci ho provato però.