Lendo um arquivo de recurso de dentro do jar

241

Gostaria de ler um recurso de dentro do meu jar da seguinte forma:

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

//Read the file

e funciona bem ao executá-lo no Eclipse, mas se eu exportá-lo para um jar, execute-o, há uma IllegalArgumentException:

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

e eu realmente não sei por que, mas com alguns testes descobri que se eu mudar

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

para

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

então funciona o contrário (funciona em jar, mas não eclipse).

Estou usando o Eclipse e a pasta com meu arquivo está em uma pasta de classe.

PrinceCJC
fonte
Se você quiser ler arquivos de um diretório no jar com qualquer número de arquivos, consulte Stackoverflow-Link
Michael Hegner 2/16
1
Não tenho certeza se a pergunta original estava envolvendo a Spring. O link no comentário anterior refere-se a uma resposta específica do Spring de uma pergunta diferente. Eu acredito que getResourceAsStreamainda é uma solução mais simples e mais portátil para o problema.
Tirou MacInnis

Respostas:

397

Em vez de tentar endereçar o recurso como um arquivo, peça ao ClassLoader para retornar um InputStream para o recurso, em vez de getResourceAsStream :

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

Enquanto o file.txtrecurso estiver disponível no caminho de classe, essa abordagem funcionará da mesma maneira, independentemente de o file.txtrecurso estar em um classes/diretório ou dentro de um jar.

A URI is not hierarchicalocorre porque o URI de um recurso dentro de um arquivo jar vai ser algo como isto: file:/example.jar!/file.txt. Você não pode ler as entradas em um jar(um ziparquivo) como se fosse um arquivo antigo simples .

Isso é bem explicado pelas respostas para:

Drew MacInnis
fonte
3
Obrigado, isso foi muito útil e o código funciona perfeitamente, mas eu tenho um problema, preciso determinar se InputStreamexiste (como File.exists()) para que meu jogo possa dizer se deve usar o arquivo padrão ou não. Obrigado.
PrinceCJC
1
Ah, e BTW, o motivo pelo qual getClass().getResource("**/folder**/file.txt")ele funcionou é porque eu tinha essa pasta no mesmo diretório do meu jar :).
PrinceCJC
5
getResourceAsStreamretorna nulo se o recurso não existir, para que possa ser o seu teste "existe".
Drew MacInnis
1
BTW, você tem um erro de digitação: deve ser BufferedReader, não BufferredReader (aviso do extra 'r' no mais tarde)
mailmindlin
2
E, claro ... não se esqueça de fechar a inputStream e BufferedReader
Noremac
26

Para acessar um arquivo em um jar, você tem duas opções:

  • Coloque o arquivo na estrutura de diretórios que corresponde ao nome do pacote (após extrair o arquivo .jar, ele deve estar no mesmo diretório que o arquivo .class) e acesse-o usando getClass().getResourceAsStream("file.txt")

  • Coloque o arquivo na raiz (depois de extrair o arquivo .jar, ele deve estar na raiz) e acesse-o usando Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

A primeira opção pode não funcionar quando o jar é usado como um plug-in.

Juozas Kontvainis
fonte
Muito obrigado por esta ótima resposta ... Outro benefício da segunda opção é que ela também funciona com o método principal, porque getClass () funciona apenas a partir de uma instância e não de métodos estáticos.
Dan Ortega
6

Eu já tinha esse problema e abri caminho para o carregamento. Basicamente, a primeira maneira de trabalhar no arquivo .jar e a segunda maneira funcionam no eclipse ou em outro 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;
    }
}
MForm
fonte
3

Até agora (dezembro de 2017), esta é a única solução que eu encontrei que funciona tanto dentro como fora do IDE.

Use PathMatchingResourcePatternResolver

Nota: também funciona em boot de mola

Neste exemplo, estou lendo alguns arquivos localizados em 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);
}
Naor Bar
fonte
3

O problema é que certas bibliotecas de terceiros exigem nomes de caminhos de arquivos em vez de fluxos de entrada. A maioria das respostas não aborda esse problema.

Nesse caso, uma solução alternativa é copiar o conteúdo do recurso em um arquivo temporário. O exemplo a seguir usa 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();
    }
Navneeth
fonte
Isso é encoberto muito. Obrigado por documentá-lo. Estou curioso para saber quais outras opções existem sem o JUnit.
Alex Moore-Niemi
0

Se você quiser ler como um arquivo, acredito que ainda exista uma solução semelhante:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());
pablo.vix
fonte
4
URL.getFile () não converte uma URL em um nome de arquivo. Ele retorna a parte da URL após o host, com todas as porcentagens de codificação intactas; portanto, se o caminho contiver caracteres não ASCII ou caracteres ASCII não permitidos em URLs (incluindo espaços), o resultado não será um nome de arquivo existente. , mesmo se o URL for um file:URL.
VGR 05/03
48
isso não funciona dentro de uma vez que o programa é de construção para um frasco
Akshay Kasar
1
Não funciona no jar, a menos que você converta em string e salve-o localmente primeiro.
Smoosh911
0

Certifique-se de trabalhar com o separador correto. Substituí tudo /em um caminho relativo por a File.separator. Isso funcionou bem no IDE, no entanto, não funcionou no JAR de compilação.

Petterson
fonte
0

Eu acho que isso deve funcionar em java também. O código a seguir que eu uso está usando o kotlin.

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

Eu encontrei uma correção

BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream(path)));

Substitua "Main" pela classe java em que você o codificou. Substitua "path" pelo caminho dentro do arquivo jar.

por exemplo, se você colocar State1.txt no pacote com.issac.state, digite o caminho como "/ com / issac / state / State1" se você executar Linux ou Mac. Se você executar o Windows, digite o caminho como "\ com \ issac \ state \ State1". Não adicione a extensão .txt ao arquivo, a menos que ocorra a exceção Arquivo não encontrado.

nulo
fonte
-1

Você pode usar o carregador de classes que lerá o caminho de classe como caminho ROOT (sem "/" no início)

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

Se você estiver usando o spring, poderá usar o seguinte método para ler o arquivo em 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();
  }
Sujan M.
fonte
1
Bem-vindo ao SO. Isso não fornece uma resposta para a pergunta. Depois de ter reputação suficiente, você poderá comentar qualquer postagem. Verifique também o que posso fazer .
thewaywewere
Isso removerá as quebras de linha no arquivo!
Elad.chen 3/03/19
-1

Por alguma razão, classLoader.getResource()sempre retornava nulo quando implantei o aplicativo Web no WildFly 14. obtendo classLoader de getClass().getClassLoader()ou Thread.currentThread().getContextClassLoader()retorna nulo.

getClass().getClassLoader() O documento da API diz:

"Retorna o carregador de classes da classe. Algumas implementações podem usar null para representar o carregador de classes de inicialização. Este método retornará nulo nessas implementações se essa classe tiver sido carregada pelo carregador de classes de inicialização."

Pode ser que você esteja usando o WildFly e seu aplicativo da Web, tente isso

request.getServletContext().getResource()retornou o URL do recurso. Aqui request é um objeto de ServletRequest.

Ram C
fonte
-2

O código abaixo funciona com o Spring boot (kotlin):

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