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 Path
APIs mit Klassenpfadressourcen verwenden.
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 Path
APIs mit Klassenpfadressourcen verwenden.
Antworten:
Dieser funktioniert für mich:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
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();
class.getResource
erfordert aber in meinem Fall einen Schrägstrich, getSystemResourceAsStream
kann die Datei jedoch nicht finden, wenn ein Schrägstrich vorangestellt ist.
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 file
URIs 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.lang
im Beispiel) wie Path
folgt 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.class
fü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.
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.get
jedoch 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.
newFileSystem
dazu 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 Path
Sie erhalten ClosedFileSystemException
. @ Holger Antwort funktioniert gut für mich.
FileSystem
. Wenn Sie eine Ressource aus einem Glas laden, und Sie erstellen Sie dann die erforderliche FileSystem
- das FileSystem
erlaubt 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
.
#getPath(String)
Methode nicht für das FileSystem
Objekt verwenden.
Ich habe eine kleine Hilfsmethode geschrieben, um Paths
aus 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());
}
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);
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();
}
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)) {
....
}
}
Paths.get(URI)
wenn Sie den langen Weg nehmen (Wortspiel beabsichtigt), haben Sie dann ´URL.toURI (), and last
getResource () `, das a zurückgibtURL
. Möglicherweise können Sie diese miteinander verketten. Habe es aber nicht versucht.