Czy istnieje interfejs API, aby uzyskać zasób ścieżki klas (np. Z tego, z czego otrzymam Class.getResource(String)
) jako plik java.nio.file.Path
? Idealnie, chciałbym używać nowych, fantazyjnych Path
API z zasobami classpath.
Czy istnieje interfejs API, aby uzyskać zasób ścieżki klas (np. Z tego, z czego otrzymam Class.getResource(String)
) jako plik java.nio.file.Path
? Idealnie, chciałbym używać nowych, fantazyjnych Path
API z zasobami classpath.
Odpowiedzi:
Ten działa dla mnie:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
Domyślam się, że to, co chcesz zrobić, to wywołanie Files.lines (...) na zasobie pochodzącym ze ścieżki klas - prawdopodobnie z słoika.
Ponieważ Oracle zagmatwało pojęcie, kiedy ścieżka jest ścieżką, nie zmuszając getResource do zwracania użytecznej ścieżki, jeśli znajduje się ona w pliku jar, musisz zrobić coś takiego:
Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
class.getResource
znak „/”, nie wiem, ale w moim przypadku wymaga ukośnika, ale getSystemResourceAsStream
nie mogę znaleźć pliku poprzedzonego ukośnikiem.
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 poradzenie sobie z dwiema możliwościami, albo posiadanie istniejącego systemu plików, którego powinniśmy używać, ale nie zamykania (jak w przypadku file
identyfikatorów URI lub pamięci modułów Java 9), albo konieczność samodzielnego otwierania, a tym samym bezpiecznego zamykania systemu plików (np. zip / jar).
Dlatego powyższe rozwiązanie hermetyzuje rzeczywistą akcję w pliku interface
, obsługuje oba przypadki, bezpiecznie zamykając później w drugim przypadku i działa od Java 7 do Java 10. Sprawdza, czy istnieje już otwarty system plików przed otwarciem nowego, 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żna go używać we wszystkich wymienionych powyżej wersjach Java, np. Do wypisywania zawartości pakietu ( java.lang
w przykładzie) jako Path
s, na przykład:
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);
}
}
});
W Javie 8 lub nowszym można używać wyrażeń lambda lub odwołań do metod, aby przedstawić rzeczywistą akcję, np
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
try(Stream<Path> stream = Files.list(path.getParent())) {
stream.forEach(System.out::println);
}
});
zrobić to samo.
Ostateczna wersja systemu modułów Java 9 zepsuła powyższy przykład 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/Object.class
. Można to naprawić, dodając brakującą wartość, /modules/
gdy ścieżka nadrzędna jest zgłaszana jako nieistniejąca:
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);
}
});
Następnie będzie ponownie działać ze wszystkimi wersjami i metodami przechowywania.
Okazuje się, że możesz to zrobić z pomocą wbudowanego dostawcy systemu plików Zip . Jednak przekazanie identyfikatora URI zasobu bezpośrednio do Paths.get
nie zadziała; zamiast tego należy najpierw utworzyć system plików zip dla identyfikatora URI jar bez nazwy wpisu, a następnie odnieść 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);
}
Aktualizacja:
Słusznie wskazano, że powyższy kod zawiera wyciek zasobów, ponieważ kod otwiera nowy obiekt FileSystem, ale nigdy go nie zamyka. Najlepszym podejściem jest przekazanie obiektu roboczego podobnego do Konsumenta, podobnie jak robi to odpowiedź Holgera. Otwórz system plików ZipFS na tyle długo, aby pracownik mógł zrobić wszystko, co musi zrobić ze ścieżką (o ile pracownik nie będzie próbował przechowywać obiektu ścieżki do późniejszego użycia), a następnie zamknij system plików.
newFileSystem
może prowadzić do tego, że wiele zasobów będzie pozostawać otwartych na zawsze. Chociaż dodatek @raisercostin pozwala uniknąć błędu podczas próby utworzenia już utworzonego systemu plików, jeśli spróbujesz użyć zwróconego Path
, otrzymasz plik ClosedFileSystemException
. Odpowiedź @Holger działa dobrze dla mnie.
FileSystem
. Jeśli załadujesz zasób z słoika, a następnie utworzysz wymagany FileSystem
- FileSystem
pozwoli ci to również załadować inne zasoby z tego samego słoika. Po utworzeniu nowego FileSystem
zasobu możesz po prostu spróbować ponownie załadować zasób za pomocą, Paths.get(Path)
a implementacja automatycznie użyje nowego FileSystem
.
#getPath(String)
metody na FileSystem
obiekcie.
Napisałem małą metodę pomocniczą do odczytu Paths
z zasobów Twojej klasy. Jest to bardzo wygodne w użyciu, ponieważ wymaga jedynie odniesienia do klasy, w której przechowujesz zasoby, oraz nazwy samego zasobu.
public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
URL url = resourceClass.getResource(resourceName);
return Paths.get(url.toURI());
}
Nie można utworzyć identyfikatora URI z zasobów znajdujących się w pliku jar. Możesz po prostu zapisać go w pliku tymczasowym, a następnie użyć (java8):
Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
Odczytaj plik z folderu zasobów za pomocą NIO, w 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();
}
Musisz zdefiniować system plików, aby odczytywać zasoby z pliku jar, jak wspomniano w https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . Udało mi się odczytać zasób z pliku jar z poniższymi kodami:
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)
´URL.toURI (), and last
getResource () `, która zwraca plikURL
. Możesz połączyć je w łańcuch. Jednak nie próbowałem.