java.nio.file.Path per una risorsa del percorso di classe


143

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 PathAPI con risorse per i percorsi di classe.


3
Bene, prendendo il lungo percorso (gioco di Paths.get(URI)parole previsto), hai , quindi ´URL.toURI () , and last getResource () `che restituisce a URL. Potresti riuscire a metterli insieme. Non ci ho provato però.
NilsH,

Risposte:


174

Questo funziona per me:

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

7
@VGR se le risorse nel file .jar potrebbero provare questa risorsa di risorsa = new ClassPathResource ("use.txt"); Lettore BufferedReader = new BufferedReader (nuovo InputStreamReader (resource.getInputStream ())); `vedere stackoverflow.com/questions/25869428/...
zhuguowei

8
@zhuguowei è un approccio specifico per la primavera. Non funziona affatto quando Spring non viene utilizzata.
Ryan J. McDonough,

2
Se la tua applicazione non si basa sul classloader di sistema, dovrebbe essereThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA

27

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

1
se il precedente "/" è necessario nel tuo caso, non lo so, ma nel mio caso class.getResourcerichiede una barra ma getSystemResourceAsStreamnon riesco a trovare il file quando è preceduto da una barra.
Adam,

11

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.langnell'esempio) come Paths, 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.classper 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.


1
Questa soluzione funziona alla grande! Posso confermare che funziona con tutte le risorse (file, directory) sia nei percorsi di classe delle directory che nei percorsi di classe Jar. Questo è sicuramente il modo in cui copiare molte risorse in Java 7+.
Mitchell Skaggs,

10

Si scopre che è possibile farlo, con l'aiuto del provider di file system Zip integrato . Tuttavia, passare un URI di risorsa direttamente a Paths.getnon 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.


11
Fai attenzione alle fs appena create. Una seconda chiamata usando lo stesso jar genererà un'eccezione lamentandosi di un filesystem già esistente. Sarà meglio provare (FileSystem fs = ...) {return fs.getPath (entryName);} o se vuoi che questa cache sia gestita in modo più avanzato. Nella forma attuale è rischioso.
raisercostin,

3
Oltre alla questione del nuovo filesystem potenzialmente non chiuso, le ipotesi sulla relazione tra gli schemi e la necessità di aprire un nuovo filesystem e la confusione con i contenuti dell'URI limitano l'utilità della soluzione. Ho impostato una nuova risposta che mostra un approccio generale che semplifica l'operazione e gestisce contemporaneamente nuovi schemi come il nuovo storage di classe Java 9. Funziona anche quando qualcun altro all'interno dell'applicazione ha già aperto il filesystem (o il metodo viene chiamato due volte per lo stesso jar) ...
Holger

A seconda dell'utilizzo di questa soluzione, il non chiuso newFileSystempuò 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 Pathverrà visualizzato un ClosedFileSystemException. La risposta di @Holger funziona bene per me.
José Andias,

Non chiuderei il file FileSystem. Se carichi una risorsa da un Jar e poi crei il necessario FileSystem, ciò FileSystemti 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.
NS du Toit

Cioè non è necessario utilizzare il #getPath(String)metodo FileSystemsull'oggetto.
NS du Toit,

5

Ho scritto un piccolo metodo di supporto per leggere Pathsle 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());
}  

1

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

1

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.