Чтение файла ресурсов изнутри банки

Я хотел бы прочитать ресурс из моей банки, например, так:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));

//Read the file

и он отлично работает при запуске в Eclipse, но если я экспортирую его в jar файл, то при запуске возникает IllegalArgumentException:

Exception in thread "Thread-2"
java.lang.IllegalArgumentException: URI is not hierarchical

и я действительно не знаю почему, но с некоторым тестированием я обнаружил, если я изменюсь

file = new File(getClass().getResource("/file.txt").toURI());

в

file = new File(getClass().getResource("/folder/file.txt").toURI());

тогда это работает наоборот (это работает в банке, но не затмение).

Я использую Eclipse, и папка с моим файлом находится в папке класса.

Ответ 1

Вместо того, чтобы пытаться обратиться к ресурсу как к файлу, просто попросите ClassLoader вернуть InputStream для ресурса вместо этого через getResourceAsStream:

InputStream in = getClass().getResourceAsStream("/file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

Пока ресурс file.txt доступен в пути к классам, этот подход будет работать одинаково независимо от того, находится file.txt ресурс file.txt в каталоге classes/ или внутри jar.

URI is not hierarchical потому что URI для ресурса в jar файле будет выглядеть примерно так: file: /example.jar!/file.txt. Вы не можете прочитать записи в jar (zip файле), как если бы это был обычный старый файл.

Это хорошо объясняется ответами на:

Ответ 2

Чтобы получить доступ к файлу в банке, у вас есть два варианта:

  • Поместите файл в структуру каталогов, соответствующую имени вашего пакета (после извлечения файла .jar, он должен находиться в том же каталоге, что и файл .class), затем обращайтесь к нему с помощью getClass().getResourceAsStream("file.txt")

  • Поместите файл в корневой каталог (после извлечения файла .jar, он должен быть в корневом каталоге), затем получите доступ к нему с помощью Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

Первый вариант может не работать, когда jar используется в качестве плагина.

Ответ 3

Если вы хотите читать как файл, я считаю, что все еще есть аналогичное решение:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());

Ответ 4

У меня была эта проблема раньше, и я сделал резервный способ загрузки. В основном первый способ работы внутри .jar файла и второго способа работает внутри eclipse или другой среды IDE.

public class MyClass {

    public static InputStream accessFile() {
        String resource = "my-file-located-in-resources.txt";

        // this is the path within the jar file
        InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
        if (input == null) {
            // this is how we load file within editor (eg eclipse)
            input = MyClass.class.getClassLoader().getResourceAsStream(resource);
        }

        return input;
    }
}

Ответ 5

Вы также можете использовать java.nio. Вот пример slurp в тексте из файла в resourcePath в пути к классам:

new String(Files.readAllBytes(Paths.get(getClass().getResource(resourcePath).toURI())))

Ответ 6

Убедитесь, что вы работаете с правильным разделителем. Я заменил все / на относительный путь с помощью File.separator. Это отлично работало в среде IDE, однако не работало в JAR сборки.

Ответ 7

До сих пор (декабрь 2017 года) это единственное найденное решение, которое работает и внутри и снаружи среды разработки.

Используйте PathMatchingResourcePatternResolver

Примечание: работает также в spring -boot

В этом примере я читаю несколько файлов, расположенных в src/main/resources/my_folder:

try {
    // Get all the files under this inner resource folder: my_folder
    String scannedPackage = "my_folder/*";
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(scannedPackage);

    if (resources == null || resources.length == 0)
        log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
    else {
        for (Resource resource : resources) {
            log.info(resource.getFilename());
            // Read the file content (I used BufferedReader, but there are other solutions for that):
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                // ...
                // ...                      
            }
            bufferedReader.close();
        }
    }
} catch (Exception e) {
    throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}

Ответ 8

После долгих поисков в Java единственное решение, которое мне подходит, это вручную прочитать сам файл jar, если вы не находитесь в среде разработки (IDE):

/** @return The root folder or jar file that the class loader loaded from */
public static final File getClasspathFile() {
    return new File(YourMainClass.class.getProtectionDomain().getCodeSource().getLocation().getFile());
}

/** @param resource The path to the resource
 * @return An InputStream containing the resource contents, or
 *         <b><code>null</code></b> if the resource does not exist */
public static final InputStream getResourceAsStream(String resource) {
    resource = resource.startsWith("/") ? resource : "/" + resource;
    if(getClasspathFile().isDirectory()) {//Development environment:
        return YourMainClass.class.getResourceAsStream(resource);
    }
    final String res = resource;//Jar or exe:
    return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
        @SuppressWarnings("resource")
        @Override
        public InputStream run() {
            try {
                final JarFile jar = new JarFile(getClasspathFile());
                String resource = res.startsWith("/") ? res.substring(1) : res;
                if(resource.endsWith("/")) {//Directory; list direct contents:(Mimics normal getResourceAsStream("someFolder/") behaviour)
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    Enumeration<JarEntry> entries = jar.entries();
                    while(entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        if(entry.getName().startsWith(resource) && entry.getName().length() > resource.length()) {
                            String name = entry.getName().substring(resource.length());
                            if(name.contains("/") ? (name.endsWith("/") && (name.indexOf("/") == name.lastIndexOf("/"))) : true) {//If it a folder, we don't want the children folders, only the parent folder children!
                                name = name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
                                baos.write(name.getBytes(StandardCharsets.UTF_8));
                                baos.write('\r');
                                baos.write('\n');
                            }
                        }
                    }
                    jar.close();
                    return new ByteArrayInputStream(baos.toByteArray());
                }
                JarEntry entry = jar.getJarEntry(resource);
                InputStream in = entry != null ? jar.getInputStream(entry) : null;
                if(in == null) {
                    jar.close();
                    return in;
                }
                final InputStream stream = in;//Don't manage 'jar' with try-with-resources or close jar until the
                return new InputStream() {//returned stream is closed(closing the jar closes all associated InputStreams):
                    @Override
                    public int read() throws IOException {
                        return stream.read();
                    }

                    @Override
                    public int read(byte b[]) throws IOException {
                        return stream.read(b);
                    }

                    @Override
                    public int read(byte b[], int off, int len) throws IOException {
                        return stream.read(b, off, len);
                    }

                    @Override
                    public long skip(long n) throws IOException {
                        return stream.skip(n);
                    }

                    @Override
                    public int available() throws IOException {
                        return stream.available();
                    }

                    @Override
                    public void close() throws IOException {
                        try {
                            jar.close();
                        } catch(IOException ignored) {
                        }
                        stream.close();
                    }

                    @Override
                    public synchronized void mark(int readlimit) {
                        stream.mark(readlimit);
                    }

                    @Override
                    public synchronized void reset() throws IOException {
                        stream.reset();
                    }

                    @Override
                    public boolean markSupported() {
                        return stream.markSupported();
                    }
                };
            } catch(Throwable e) {
                e.printStackTrace();
                return null;
            }
        }
    });
}

Примечание: приведенный выше код работает правильно только для файлов JAR, если он находится в основном классе. Я не уверен почему.

Ответ 9

Я думаю, что это должно работать и в Java. Следующий код, который я использую, использует kotlin.

val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')

Ответ 10

По какой-то причине classLoader.getResource() всегда возвращал значение null, когда я развертывал веб-приложение в WildFly 14. Получение classLoader из getClass().getClassLoader() или Thread.currentThread().getContextClassLoader() возвращает null.

getClass().getClassLoader() API API getClass().getClassLoader() гласит:

"Возвращает загрузчик класса для класса. Некоторые реализации могут использовать null для представления загрузчика класса начальной загрузки. Этот метод будет возвращать null в таких реализациях, если этот класс был загружен загрузчиком класса начальной загрузки".

может быть, если вы используете WildFly и ваше веб-приложение попробуйте это

request.getServletContext().getResource() вернул URL ресурса. Здесь запрос является объектом ServletRequest.

Ответ 11

Проблема заключается в том, что некоторым сторонним библиотекам требуются имена файлов, а не потоки ввода. Большинство ответов не решают эту проблему.

В этом случае одним из обходных путей является копирование содержимого ресурса во временный файл. В следующем примере используется jUnit TemporaryFolder.

    private List<String> decomposePath(String path){
        List<String> reversed = Lists.newArrayList();
        File currFile = new File(path);
        while(currFile != null){
            reversed.add(currFile.getName());
            currFile = currFile.getParentFile();
        }
        return Lists.reverse(reversed);
    }

    private String writeResourceToFile(String resourceName) throws IOException {
        ClassLoader loader = getClass().getClassLoader();
        InputStream configStream = loader.getResourceAsStream(resourceName);
        List<String> pathComponents = decomposePath(resourceName);
        folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
        File tmpFile = folder.newFile(resourceName);
        Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
        return tmpFile.getAbsolutePath();
    }

Ответ 12

Ниже приведен код, работающий с Spring boot (kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))

Ответ 13

Если вы используете spring, вы можете использовать следующий метод для чтения файла из src/main/resources:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;

  public String readFileToString(String path) throws IOException {

    StringBuilder resultBuilder = new StringBuilder("");
    ClassPathResource resource = new ClassPathResource(path);

    try (
        InputStream inputStream = resource.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {

      String line;

      while ((line = bufferedReader.readLine()) != null) {
        resultBuilder.append(line);
      }

    }

    return resultBuilder.toString();
  }

Ответ 14

Вы можете использовать загрузчик классов, который будет читать из classpath как путь ROOT (без "/" в начале)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));