Lectura de un archivo de recursos desde jar

243

Me gustaría leer un recurso desde mi jar de la siguiente manera:

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

//Read the file

y funciona bien cuando lo ejecuto en Eclipse, pero si lo exporto a un jar y lo ejecuto, hay una IllegalArgumentException:

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

y realmente no sé por qué, pero con algunas pruebas encontré si cambio

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

a

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

entonces funciona al contrario (funciona en jar pero no eclipse).

Estoy usando Eclipse y la carpeta con mi archivo está en una carpeta de clase.

PrinceCJC
fuente
Si desea leer archivos de un directorio en jar con cualquier número de archivos, vea Stackoverflow-Link
Michael Hegner el
1
No estoy seguro de que la pregunta original involucrara a Spring. El enlace en el comentario anterior se refiere a una respuesta específica de Spring de una pregunta diferente. Creo que getResourceAsStreamsigue siendo una solución más simple y más portátil para el problema.
Drew MacInnis

Respuestas:

398

En lugar de tratar de abordar el recurso como un archivo, simplemente solicite al ClassLoader que devuelva un InputStream para el recurso en su lugar a través de getResourceAsStream :

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

Mientras el file.txtrecurso esté disponible en el classpath, este enfoque funcionará de la misma manera, independientemente de si el file.txtrecurso está en un classes/directorio o dentro de a jar.

El URI is not hierarchicalse produce porque la URI de un recurso dentro de un archivo JAR se va a ver algo como esto: file:/example.jar!/file.txt. No puede leer las entradas dentro de un jar(un ziparchivo) como si fuera un archivo antiguo simple .

Esto se explica bien por las respuestas a:

Drew MacInnis
fuente
3
Gracias, esto fue muy útil y el código funciona perfectamente, pero tengo un problema, necesito determinar si InputStreamexiste (me gusta File.exists()) para que mi juego pueda saber si usar el archivo predeterminado o no. Gracias.
PrinceCJC
1
Ah, y por cierto, la razón por la getClass().getResource("**/folder**/file.txt")que funcionó es porque tenía esa carpeta en el mismo directorio que mi jar :).
PrinceCJC
55
getResourceAsStreamdevuelve nulo si el recurso no existe para que pueda ser su prueba de "existe".
Drew MacInnis
1
Por cierto, tiene un error tipográfico: debe ser BufferedReader, no BufferredReader (observe la 'r' adicional más adelante)
mailmindlin
2
Y, por supuesto ... no te olvides de cerrar inputStream y BufferedReader
Noremac
26

Para acceder a un archivo en un jar tiene dos opciones:

  • Coloque el archivo en la estructura de directorios que coincida con el nombre de su paquete (después de extraer el archivo .jar, debe estar en el mismo directorio que el archivo .class), luego acceda a él usando getClass().getResourceAsStream("file.txt")

  • Coloque el archivo en la raíz (después de extraer el archivo .jar, debe estar en la raíz), luego acceda usando Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

Es posible que la primera opción no funcione cuando jar se usa como complemento.

Juozas Kontvainis
fuente
Muchas gracias por esta gran respuesta ... Otro beneficio de la segunda opción es que también funciona desde el método principal, porque getClass () solo funciona desde una instancia y no desde métodos estáticos.
Dan Ortega
6

Tuve este problema antes e hice una forma alternativa de cargar. Básicamente, la primera forma de trabajar dentro del archivo .jar y la segunda forma de trabajo dentro del eclipse u otro 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
fuente
3

Hasta ahora (diciembre de 2017), esta es la única solución que encontré que funciona tanto dentro como fuera del IDE.

Utilice PathMatchingResourcePatternResolver

Nota: funciona también en spring-boot

En este ejemplo, estoy leyendo algunos archivos ubicados en 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);
}
Bar Naor
fuente
3

El problema es que ciertas bibliotecas de terceros requieren nombres de ruta de archivo en lugar de secuencias de entrada. La mayoría de las respuestas no abordan este problema.

En este caso, una solución es copiar el contenido del recurso en un archivo temporal. El siguiente ejemplo usa jUnit's 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
fuente
Esto se pasa por alto mucho. Gracias por documentarlo. Tengo curiosidad por saber qué otras opciones existen sin JUnit.
Alex Moore-Niemi
0

Si quieres leer como un archivo, creo que todavía hay una solución similar:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());
pablo.vix
fuente
44
URL.getFile () no convierte una URL en un nombre de archivo. Devuelve la parte de la URL después del host, con todas las codificaciones porcentuales intactas, por lo que si la ruta contiene caracteres no ASCII o caracteres ASCII no permitidos en las URL (incluidos los espacios), el resultado no será un nombre de archivo existente , incluso si la URL es una file:URL.
VGR
48
esto no funciona dentro una vez que el programa se construye en un jar
Akshay Kasar
1
No funciona desde jar a menos que convierta a cadena y lo guarde localmente primero.
smoosh911
0

Asegúrese de trabajar con el separador correcto. Reemplacé todo /en un camino relativo con a File.separator. Esto funcionó bien en el IDE, sin embargo, no funcionó en el JAR de compilación.

Petterson
fuente
0

Creo que esto también debería funcionar en Java. El siguiente código que uso está usando kotlin.

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

He encontrado una solución

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

Reemplace "Principal" con la clase de Java en la que lo codificó. Reemplace "ruta" con la ruta dentro del archivo jar.

por ejemplo, si coloca State1.txt en el paquete com.issac.state, escriba la ruta como "/ com / issac / state / State1" si ejecuta Linux o Mac. Si ejecuta Windows, escriba la ruta como "\ com \ issac \ state \ State1". No agregue la extensión .txt al archivo a menos que ocurra la excepción Archivo no encontrado.

nulo
fuente
-1

Puede usar el cargador de clases que leerá desde classpath como ruta ROOT (sin "/" al principio)

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

Si está utilizando spring, puede usar el siguiente método para leer el archivo de 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.
fuente
1
Bienvenido a SO. Esto no proporciona una respuesta a la pregunta. Una vez que tengas suficiente reputación podrás comentar cualquier publicación. También verifique esto, ¿qué puedo hacer en su lugar ?
2017
¡Esto eliminará los saltos de línea en el archivo!
elad.chen
-1

Por alguna razón, classLoader.getResource()siempre regresó nulo cuando implementé la aplicación web en WildFly 14. obteniendo classLoader getClass().getClassLoader()o Thread.currentThread().getContextClassLoader()devuelve nulo.

getClass().getClassLoader() API doc dice:

"Devuelve el cargador de clases para la clase. Algunas implementaciones pueden usar nulo para representar el cargador de clases bootstrap. Este método devolverá nulo en tales implementaciones si el cargador de clases bootstrap cargó esta clase".

puede ser si está utilizando WildFly y su aplicación web intente esto

request.getServletContext().getResource()devolvió la URL del recurso. Aquí la solicitud es un objeto de ServletRequest.

Ram C
fuente
-2

El siguiente código funciona con Spring boot (kotlin):

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