Apakah ada API untuk mendapatkan sumber daya classpath (misalnya apa yang akan saya dapatkan Class.getResource(String)
) sebagai java.nio.file.Path
? Idealnya, saya ingin menggunakan Path
API baru yang mewah dengan sumber daya classpath.
Apakah ada API untuk mendapatkan sumber daya classpath (misalnya apa yang akan saya dapatkan Class.getResource(String)
) sebagai java.nio.file.Path
? Idealnya, saya ingin menggunakan Path
API baru yang mewah dengan sumber daya classpath.
Jawaban:
Yang ini bekerja untuk saya:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
Menebak apa yang ingin Anda lakukan, adalah panggilan Files.lines (...) pada sumber yang berasal dari classpath - mungkin dari dalam toples.
Karena Oracle berbelit-belit ketika Path adalah Path dengan tidak membuat getResource mengembalikan path yang dapat digunakan jika berada di file jar, yang perlu Anda lakukan adalah sesuatu seperti ini:
Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
class.getResource
membutuhkan garis miring tetapi getSystemResourceAsStream
tidak dapat menemukan file ketika diawali dengan garis miring.
Solusi paling umum adalah sebagai berikut:
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);
}
}
}
Kendala utama adalah untuk menangani dua kemungkinan, baik, memiliki sistem file yang sudah ada yang harus kita gunakan, tetapi tidak menutup (seperti dengan file
URI atau penyimpanan modul Java 9), atau harus membuka dan dengan demikian dengan aman menutup sistem file sendiri (seperti file zip / jar).
Oleh karena itu, solusi di atas merangkum tindakan aktual dalam interface
, menangani kedua kasus, dengan aman menutup setelahnya dalam kasus kedua, dan bekerja dari Java 7 ke Java 10. Ini menyelidiki apakah sudah ada filesystem terbuka sebelum membuka yang baru, sehingga juga berfungsi jika komponen lain dari aplikasi Anda telah membuka sistem file untuk file zip / jar yang sama.
Itu dapat digunakan di semua versi Java yang disebutkan di atas, misalnya untuk membuat daftar isi suatu paket ( java.lang
dalam contoh) sebagai Path
s, seperti ini:
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);
}
}
});
Dengan Java 8 atau lebih baru, Anda dapat menggunakan ekspresi lambda atau referensi metode untuk mewakili tindakan aktual, misalnya
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
try(Stream<Path> stream = Files.list(path.getParent())) {
stream.forEach(System.out::println);
}
});
untuk melakukan hal yang sama.
Rilis terakhir sistem modul Java 9 telah memecahkan contoh kode di atas. JRE tidak konsisten mengembalikan jalan /java.base/java/lang/Object.class
untuk Object.class.getResource("Object.class")
sementara itu harus /modules/java.base/java/lang/Object.class
. Ini dapat diperbaiki dengan menambahkan sebelumnya yang hilang /modules/
ketika jalur induk dilaporkan sebagai tidak ada:
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);
}
});
Kemudian, itu akan kembali berfungsi dengan semua versi dan metode penyimpanan.
Ternyata Anda dapat melakukan ini, dengan bantuan penyedia Sistem File Zip bawaan . Namun, melewatkan URI sumber daya secara langsung ke Paths.get
tidak akan berhasil; sebagai gantinya, pertama-tama seseorang harus membuat sistem file zip untuk toples URI tanpa nama entri, kemudian merujuk ke entri dalam sistem file itu:
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);
}
Memperbarui:
Sudah ditunjukkan dengan benar bahwa kode di atas mengandung kebocoran sumber daya, karena kode membuka objek FileSystem baru tetapi tidak pernah menutupnya. Pendekatan terbaik adalah dengan melewatkan objek pekerja yang mirip konsumen, seperti halnya jawaban Holger melakukannya. Buka ZipFS FileSystem cukup lama bagi pekerja untuk melakukan apa pun yang perlu dilakukan dengan Path (selama pekerja tidak mencoba untuk menyimpan objek Path untuk digunakan nanti), kemudian tutup FileSystem.
newFileSystem
dapat menyebabkan banyak sumber daya nongkrong terbuka selamanya. Meskipun addendum @raisercostin menghindari kesalahan saat mencoba membuat sistem file yang sudah dibuat, jika Anda mencoba menggunakan yang dikembalikan Path
Anda akan mendapatkan ClosedFileSystemException
. @ Tanggapan Holger bekerja dengan baik untuk saya.
FileSystem
. Jika Anda memuat sumber daya dari Jar, dan Anda kemudian membuat yang diperlukan FileSystem
- FileSystem
itu juga akan memungkinkan Anda untuk memuat sumber daya lainnya dari Jar yang sama. Juga, setelah Anda membuat yang baru, FileSystem
Anda bisa mencoba memuat sumber daya lagi menggunakan Paths.get(Path)
dan implementasi akan secara otomatis menggunakan yang baru FileSystem
.
#getPath(String)
metode pada FileSystem
objek.
Saya menulis metode pembantu kecil untuk membaca Paths
dari sumber daya kelas Anda. Ini cukup mudah digunakan karena hanya membutuhkan referensi kelas yang telah Anda simpan sumber daya Anda serta nama sumber daya itu sendiri.
public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
URL url = resourceClass.getResource(resourceName);
return Paths.get(url.toURI());
}
Anda tidak dapat membuat URI dari sumber daya di dalam file jar. Anda cukup menulisnya ke file temp dan kemudian menggunakannya (java8):
Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
Baca File dari folder sumber daya menggunakan NIO, di 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();
}
Anda perlu mendefinisikan Filesystem untuk membaca sumber daya dari file jar seperti yang disebutkan di https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . Saya berhasil membaca sumber daya dari file jar dengan kode di bawah ini:
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)
, lalu ´URL.toURI (), and last
getResource () `yang mengembalikan aURL
. Anda mungkin bisa mengikatnya bersama. Belum mencoba.