java.nio.file.Path für eine Klassenpfadressource

143

Gibt es eine API, um eine Klassenpfadressource (z. B. was ich erhalten würde Class.getResource(String)) als zu erhalten java.nio.file.Path? Idealerweise möchte ich die ausgefallenen neuen PathAPIs mit Klassenpfadressourcen verwenden.

Louis Wasserman
quelle
3
Nun, Paths.get(URI)wenn Sie den langen Weg nehmen (Wortspiel beabsichtigt), haben Sie dann ´URL.toURI () , and last getResource () `, das a zurückgibt URL. Möglicherweise können Sie diese miteinander verketten. Habe es aber nicht versucht.
NilsH

Antworten:

174

Dieser funktioniert für mich:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Keyoxy
quelle
7
@VGR, wenn Ressourcen in der JAR-Datei dies versuchen könnten `Resource resource = new ClassPathResource (" usage.txt "); BufferedReader reader = neuer BufferedReader (neuer InputStreamReader (resource.getInputStream ())); `siehe stackoverflow.com/questions/25869428/…
zhuguowei
8
@zhuguowei das ist ein Frühlingsspezifischer Ansatz. Es funktioniert überhaupt nicht, wenn Spring nicht verwendet wird.
Ryan J. McDonough
2
Wenn Ihre Anwendung nicht auf dem System Classloader Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
basiert
27

Vermutlich möchten Sie Files.lines (...) für eine Ressource aufrufen, die aus dem Klassenpfad stammt - möglicherweise aus einem Glas.

Da Oracle die Vorstellung, dass ein Pfad ein Pfad ist, verschlungen hat, indem getResource keinen verwendbaren Pfad zurückgibt, wenn er sich in einer JAR-Datei befindet, müssen Sie Folgendes tun:

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
user2163960
quelle
1
Ob das vorhergehende "/" in Ihrem Fall benötigt wird, weiß ich nicht, class.getResourceerfordert aber in meinem Fall einen Schrägstrich, getSystemResourceAsStreamkann die Datei jedoch nicht finden, wenn ein Schrägstrich vorangestellt ist.
Adam
11

Die allgemeinste Lösung lautet wie folgt:

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

Das Haupthindernis besteht darin, sich mit den beiden Möglichkeiten zu befassen, entweder ein vorhandenes Dateisystem zu haben, das wir verwenden, aber nicht schließen sollten (wie bei fileURIs oder dem Modulspeicher von Java 9), oder das Dateisystem selbst zu öffnen und somit sicher zu schließen (wie zip / jar-Dateien).

Daher kapselt die obige Lösung die eigentliche Aktion in a interface, behandelt beide Fälle, schließt sie anschließend im zweiten Fall sicher und arbeitet von Java 7 bis Java 10. Sie prüft, ob bereits ein offenes Dateisystem vorhanden ist, bevor ein neues geöffnet wird funktioniert auch für den Fall, dass eine andere Komponente Ihrer Anwendung bereits ein Dateisystem für dieselbe Zip / JAR-Datei geöffnet hat.

Es kann in allen oben genannten Java-Versionen verwendet werden, z. B. um den Inhalt eines Pakets ( java.langim Beispiel) wie Pathfolgt aufzulisten :

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

Mit Java 8 oder neuer können Sie Lambda-Ausdrücke oder Methodenreferenzen verwenden, um die tatsächliche Aktion darzustellen, z

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

das Gleiche tun.


Die endgültige Version des Java 9-Modulsystems hat das obige Codebeispiel beschädigt. Die JRE gibt den Pfad /java.base/java/lang/Object.classfür inkonsistent zurück, Object.class.getResource("Object.class")während dies der Fall sein sollte /modules/java.base/java/lang/Object.class. Dies kann behoben werden, indem das Fehlende vorangestellt wird, /modules/wenn der übergeordnete Pfad als nicht vorhanden gemeldet wird:

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

Dann funktioniert es wieder mit allen Versionen und Speichermethoden.

Holger
quelle
1
Diese Lösung funktioniert super! Ich kann bestätigen, dass dies mit allen Ressourcen (Dateien, Verzeichnisse) sowohl in Verzeichnisklassenpfaden als auch in JAR-Klassenpfaden funktioniert. Auf diese Weise sollten auf jeden Fall viele Ressourcen in Java 7+ kopiert werden.
Mitchell Skaggs
10

Es stellt sich heraus, dass Sie dies mit Hilfe des eingebauten Systems tun können Anbieters Zip-Dateisysteme . Das direkte Übergeben eines Ressourcen-URI an Paths.getjedoch funktioniert nicht. Stattdessen muss zuerst ein Zip-Dateisystem für den JAR-URI ohne den Eintragsnamen erstellt und dann auf den Eintrag in diesem Dateisystem verwiesen werden:

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

Aktualisieren:

Es wurde zu Recht darauf hingewiesen, dass der obige Code ein Ressourcenleck enthält, da der Code ein neues FileSystem-Objekt öffnet, es jedoch niemals schließt. Der beste Ansatz besteht darin, ein verbraucherähnliches Arbeiterobjekt zu übergeben, ähnlich wie es Holgers Antwort tut. Öffnen Sie das ZipFS-Dateisystem gerade lange genug, damit der Worker alles tun kann, was er mit dem Pfad tun muss (solange der Worker nicht versucht, das Path-Objekt für die spätere Verwendung zu speichern), und schließen Sie dann das Dateisystem.

VGR
quelle
11
Seien Sie vorsichtig mit den neu erstellten fs. Ein zweiter Aufruf mit demselben JAR löst eine Ausnahme aus, die sich über ein bereits vorhandenes Dateisystem beschwert. Es ist besser, es mit (FileSystem fs = ...) {return fs.getPath (entryName);} zu versuchen, oder wenn Sie dieses Cache haben möchten, führen Sie eine erweiterte Behandlung durch. In der aktuellen Form ist riskant.
Rosercostin
3
Neben dem Problem des möglicherweise nicht geschlossenen neuen Dateisystems schränken die Annahmen über die Beziehung zwischen Schemata und die Notwendigkeit, ein neues Dateisystem zu öffnen, und das Rätsel mit den URI-Inhalten den Nutzen der Lösung ein. Ich habe eine neue Antwort eingerichtet, die einen allgemeinen Ansatz zeigt, der die Operation vereinfacht und gleichzeitig neue Schemata wie den neuen Java 9-Klassenspeicher behandelt. Es funktioniert auch, wenn jemand anderes in der Anwendung das Dateisystem bereits geöffnet hat (oder die Methode zweimal für dasselbe Glas aufgerufen wird)…
Holger
Abhängig von der Verwendung dieser Lösung kann das Nicht-Schließen newFileSystemdazu führen, dass mehrere Ressourcen für immer offen bleiben. Obwohl @raisercostin Nachtrag vermeidet den Fehler , wenn ein bereits angelegtes Dateisystem zu erstellen versuchen, wenn Sie versuchen , das zu verwenden , zurück PathSie erhalten ClosedFileSystemException. @ Holger Antwort funktioniert gut für mich.
José Andias
Ich würde das nicht schließen FileSystem. Wenn Sie eine Ressource aus einem Glas laden, und Sie erstellen Sie dann die erforderliche FileSystem- das FileSystemerlaubt es Ihnen auch andere Ressourcen aus dem gleichen Glas zu laden. Sobald Sie die neue erstellt haben FileSystem, können Sie einfach versuchen, die Ressource erneut mit zu laden, Paths.get(Path)und die Implementierung verwendet die neue automatisch FileSystem.
NS du Toit
Das heißt, Sie müssen die #getPath(String)Methode nicht für das FileSystemObjekt verwenden.
NS du Toit
5

Ich habe eine kleine Hilfsmethode geschrieben, um Pathsaus Ihren Klassenressourcen zu lesen . Die Verwendung ist sehr praktisch, da nur eine Referenz der Klasse benötigt wird, in der Sie Ihre Ressourcen gespeichert haben, sowie der Name der Ressource selbst.

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

Sie können keine URI aus Ressourcen in der JAR-Datei erstellen. Sie können es einfach in die temporäre Datei schreiben und dann verwenden (java8):

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

Lesen Sie eine Datei aus dem Ressourcenordner mit 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();
    }
Echter Codierer
quelle
0

Sie müssen das Dateisystem definieren, um Ressourcen aus der JAR-Datei zu lesen, wie unter https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html beschrieben . Es gelingt mir, die Ressource aus der JAR-Datei mit den folgenden Codes zu lesen:

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)) {
            ....
        }
    }
user3050291
quelle