2013-03-29 15 views
88

Czy istnieje interfejs API, aby uzyskać zasoby klasy classpath (np. Co otrzymam z Class.getResource(String)) jako java.nio.file.Path? Idealnie, chciałbym użyć nowych, fantazyjnych API Path z zasobami classpath.java.nio.file.Path dla zasobu classpath

+3

Cóż, biorąc długą drogę (gra słów nie przeznaczonych), masz 'Paths.get (URI)', następnie'URL.toURI() 'i ostatni' getResource() ', który zwraca' URL'. Możesz być w stanie połączyć je razem. Nie próbowałem jednak. – NilsH

Odpowiedz

95

Ten działa dla mnie:

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

Nie będzie działać z zasobami w plikach .jar. – VGR

+3

@VGR, jeśli zasoby w pliku .jar mogą wypróbować to '\t \t \t Zasób zasobów = nowy ClassPathResource (" usage.txt "); \t \t Czytnik buforów odczytu = nowy buforowany czytnik (nowy InputStreamReader (resource.getInputStream())); 'zobacz http://stackoverflow.com/questions/25869428/classpath-resource-not-found-when-running-as- jar – zhuguowei

8

Okazuje się, że możesz to zrobić, korzystając z wbudowanego Zip File System provider. Jednak przekazanie identyfikatora URI zasobu bezpośrednio do Paths.get nie będzie działać; Zamiast tego, trzeba najpierw stworzyć system plików zip na jar URI bez nazwy wejścia, a następnie odwołać się do wpisu w tym systemie plików:

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

Uważaj na nowo utworzone fs. Drugie wywołanie używające tego samego słoika rzuci wyjątek narzekający na już istniejący system plików. Lepiej będzie wypróbować (FileSystem fs = ...) {return fs.getPath (entryName);} lub jeśli chcesz, aby ta pamięć podręczna była bardziej zaawansowana. W obecnej formie jest ryzykowne. – raisercostin

+1

Poza kwestią potencjalnie niezamkniętego nowego systemu plików, założenia dotyczące związku między schematami i konieczności otwarcia nowego systemu plików oraz zagadki z zawartością URI ograniczają użyteczność rozwiązania. Skonfigurowałem [nową odpowiedź] (http://stackoverflow.com/a/36021165/2711488), która pokazuje ogólne podejście, które upraszcza działanie i obsługuje nowe schematy, takie jak nowe przechowywanie klasy Java 9 w tym samym czasie. Działa również wtedy, gdy ktoś inny w aplikacji już otworzył system plików (lub metoda jest wywoływana dwukrotnie dla tego samego słoika) ... – Holger

+0

W zależności od zastosowania tego rozwiązania, niezamknięty 'newFileSystem' może prowadzić do wielu zasobów w zawieszeniu na zawsze. Chociaż @raisercostin addendum omija błąd podczas próby utworzenia już utworzonego systemu plików, jeśli spróbujesz użyć zwróconej 'Path', otrzymasz' ClosedFileSystemException'. Odpowiedź @Holger działa dobrze dla mnie. –

3

pisałem mała pomocnicza metoda odczytu Paths z zasobów twojej klasy. Jest dość przydatny w użyciu, ponieważ wymaga jedynie odniesienia do klasy, w której przechowywane są twoje zasoby, jak również nazwy samego zasobu.

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

Zgadywanie, że to, co chcesz zrobić, to wywołanie Files.lines (...) na zasobie pochodzącym ze ścieżki klas - prawdopodobnie z poziomu słoika.

Od Oracle zawiłe pojęcia kiedy ścieżka jest ścieżką, nie czyniąc getResource powrócić użytkowej ścieżkę, jeśli przebywa w pliku jar, co trzeba zrobić, to coś takiego:

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines(); 
+1

, czy poprzedni "/" jest potrzebny w twoim przypadku, nie wiem, ale w moim przypadku 'class.getResource' wymaga ukośnika, ale' getSystemResourceAsStream' nie może znaleźć pliku po przedrostku z ukośnikiem. – Adam

7

najbardziej ogólne rozwiązanie jest następujące:

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

Główną przeszkodą jest do czynienia z dwoma możliwościami, albo mając istniejący system plików, który powinniśmy używać, ale nie w pobliżu (jak w przypadku URI z file lub nadchodzącego modułu Java 9) lub konieczności otwarcia i bezpiecznego zamknięcia systemu plików (takich jak pliki zip/jar).

Dlatego powyższe rozwiązanie zawiera rzeczywistą akcję w interface, obsługuje oba przypadki, następnie bezpiecznie zamyka się w drugim przypadku i działa z poziomu Java 7 do Javy 9. Sprawdza, czy istnieje już otwarty system plików przed otwarciem nowy, więc działa również w przypadku, gdy inny składnik twojej aplikacji już otworzył system plików dla tego samego pliku zip/jar.

Może być używany we wszystkich wymienionych wyżej wersjach Java, np. do wymieniający zawartość pakietu (java.lang w przykładzie) jak Path s, podobnie jak to:

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

Z Java 8 lub nowszej, można użyć wyrażeń lambda lub odniesienia metoda reprezentują rzeczywiste działania, na przykład

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

zrobić to samo.


Ostateczne wydanie systemu modułu Java 9 spowodowało zerwanie powyższego przykładu kodu.JRE niekonsekwentnie zwraca ścieżkę /java.base/java/lang/Object.class dla Object.class.getResource("Object.class"), podczas gdy powinno być /modules/java.base/java/lang/AbstractMethodError.class. To może być ustalona przez poprzedzenie brakującą /modules/ gdy ścieżka rodzic jest zgłaszane jako nieistniejące:

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

Potem będzie znowu działać ze wszystkimi wersjami i metod przechowywania.

1

Nie można utworzyć identyfikatora URI z zasobów znajdujących się w pliku jar. Można po prostu zapisać go do pliku tymczasowego, a następnie użyć go (java8):

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