java.nio.file.Path untuk sumber daya classpath

143

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 PathAPI baru yang mewah dengan sumber daya classpath.

Louis Wasserman
sumber
3
Nah, mengambil jalan panjang (pun intended), Anda miliki Paths.get(URI), lalu ´URL.toURI () , and last getResource () `yang mengembalikan a URL. Anda mungkin bisa mengikatnya bersama. Belum mencoba.
NilsH

Jawaban:

174

Yang ini bekerja untuk saya:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
keyoksi
sumber
7
@VGR jika sumber daya dalam file .jar dapat mencoba `Resource resource = new ClassPathResource (" use.txt "); BufferedReader reader = BufferedReader baru (InputStreamReader baru (resource.getInputStream ())); `silakan lihat stackoverflow.com/questions/25869428/…
zhuguowei
8
@zhuguowei itu pendekatan khusus Musim Semi. Tidak berfungsi sama sekali saat Spring tidak digunakan.
Ryan J. McDonough
2
Jika aplikasi Anda tidak bergantung pada classloader sistem, seharusnyaThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA
27

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();
pengguna2163960
sumber
1
apakah "/" sebelumnya diperlukan dalam kasus Anda, saya tidak tahu, tetapi dalam kasus saya class.getResourcemembutuhkan garis miring tetapi getSystemResourceAsStreamtidak dapat menemukan file ketika diawali dengan garis miring.
Adam
11

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 fileURI 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.langdalam contoh) sebagai Paths, 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.classuntuk 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.

Holger
sumber
1
Solusi ini sangat bagus! Saya dapat mengkonfirmasi bahwa ini bekerja dengan semua sumber daya (file, direktori) di kedua classpath direktori dan classpath jar. Ini jelas bagaimana menyalin banyak sumber daya harus dilakukan di Java 7+.
Mitchell Skaggs
10

Ternyata Anda dapat melakukan ini, dengan bantuan penyedia Sistem File Zip bawaan . Namun, melewatkan URI sumber daya secara langsung ke Paths.gettidak 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.

VGR
sumber
11
Hati-hati dengan fs yang baru dibuat. Panggilan kedua menggunakan toples yang sama akan memunculkan eksepsi yang mengeluhkan sistem file yang sudah ada. Akan lebih baik untuk mencoba (FileSystem fs = ...) {return fs.getPath (entryName);} atau jika Anda ingin cache ini melakukan penanganan lebih lanjut. Dalam bentuk saat ini berisiko.
raisercostin
3
Selain masalah sistem file baru yang berpotensi tidak tertutup, asumsi tentang hubungan antara skema dan perlunya membuka sistem file baru dan kebingungan dengan konten URI membatasi kegunaan solusi. Saya telah menyiapkan jawaban baru yang menunjukkan pendekatan umum yang menyederhanakan operasi dan menangani skema baru seperti penyimpanan kelas Java 9 yang baru secara bersamaan. Ini juga berfungsi ketika orang lain dalam aplikasi telah membuka sistem file (atau metode ini disebut dua kali untuk toples yang sama) ...
Holger
Bergantung pada penggunaan solusi ini, yang tidak tertutup newFileSystemdapat 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 PathAnda akan mendapatkan ClosedFileSystemException. @ Tanggapan Holger bekerja dengan baik untuk saya.
José Andias
Saya tidak akan menutup FileSystem. Jika Anda memuat sumber daya dari Jar, dan Anda kemudian membuat yang diperlukan FileSystem- FileSystemitu juga akan memungkinkan Anda untuk memuat sumber daya lainnya dari Jar yang sama. Juga, setelah Anda membuat yang baru, FileSystemAnda bisa mencoba memuat sumber daya lagi menggunakan Paths.get(Path)dan implementasi akan secara otomatis menggunakan yang baru FileSystem.
NS du Toit
Yaitu Anda tidak harus menggunakan #getPath(String)metode pada FileSystemobjek.
NS du Toit
5

Saya menulis metode pembantu kecil untuk membaca Pathsdari 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());
}  
Michael Stauffer
sumber
1

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);
pengguna1079877
sumber
1

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();
    }
Coder nyata
sumber
0

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)) {
            ....
        }
    }
pengguna3050291
sumber