Cómo obtener una ruta a un recurso en un archivo JAR de Java

166

Estoy tratando de encontrar un camino hacia un Recurso, pero no he tenido suerte.

Esto funciona (tanto en IDE como con JAR) pero de esta manera no puedo obtener una ruta a un archivo, solo el contenido del archivo:

ClassLoader classLoader = getClass().getClassLoader();
PrintInputStream(classLoader.getResourceAsStream("config/netclient.p"));

Si hago esto:

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("config/netclient.p").getFile());

El resultado es: java.io.FileNotFoundException: file:/path/to/jarfile/bot.jar!/config/netclient.p (No such file or directory)

¿Hay alguna manera de obtener una ruta a un archivo de recursos?

no_ripcord
fuente
1
Si. Tengo una clase con la que me gustaría trabajar, una carpeta en el exterior (en caso de que quiera cambiar algún parámetro del archivo de configuración) y un JAR que oculta los archivos de configuración de implementación para el usuario (como un archivo distribuible JAR a todas las personas).
no_ripcord
1
Entonces, la clase solo recibe una RUTA a un archivo (el archivo de configuración).
no_ripcord
3
Entonces, probablemente debería hacer que esa clase trate con una secuencia de entrada, que puede obtener de cualquier fuente.
Carl Manaster
1
Sí, lo sé. Pero habría sido más claro y limpio a la inversa. Pero gracias de todos modos.
no_ripcord
1
¿Estás tratando de hacer algo como la opción n. ° 4 en esta pregunta? stackoverflow.com/questions/775389/…
erickson

Respuestas:

71

Esto es deliberado. El contenido del "archivo" puede no estar disponible como un archivo. Recuerde que está tratando con clases y recursos que pueden ser parte de un archivo JAR u otro tipo de recurso. El cargador de clases no tiene que proporcionar un identificador de archivo al recurso, por ejemplo, el archivo jar puede no haberse expandido a archivos individuales en el sistema de archivos.

Cualquier cosa que pueda hacer obteniendo un archivo java.io.File se puede hacer copiando la transmisión en un archivo temporal y haciendo lo mismo, si un archivo java.io.File es absolutamente necesario.

Neal Maloney
fuente
66
puede agregar 'rsrc:' cuando llame a su recurso para abrirlo. como nuevo archivo ("rsrc: filename.txt") esto cargará filename.txt que está empaquetado dentro de la raíz de su jarra
gipsh
63

Al cargar un recurso, asegúrese de notar la diferencia entre:

getClass().getClassLoader().getResource("com/myorg/foo.jpg") //relative path

y

getClass().getResource("/com/myorg/foo.jpg")); //note the slash at the beginning

Supongo que esta confusión está causando la mayoría de los problemas al cargar un recurso.


Además, cuando está cargando una imagen es más fácil de usar getResourceAsStream():

BufferedImage image = ImageIO.read(getClass().getResourceAsStream("/com/myorg/foo.jpg"));

Cuando realmente tiene que cargar un archivo (sin imagen) desde un archivo JAR, puede intentar esto:

File file = null;
String resource = "/com/myorg/foo.xml";
URL res = getClass().getResource(resource);
if (res.getProtocol().equals("jar")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile("tempfile", ".tmp");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        Exceptions.printStackTrace(ex);
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}

if (file != null && !file.exists()) {
    throw new RuntimeException("Error: File " + file + " not found!");
}
Tombart
fuente
3
+1 Esto funcionó para mí. Asegúrese de colocar los archivos que desea leer en la bincarpeta y vaya al directorio de la clase que carga los recursos antes de usar la ruta `/com/myorg/filename.ext '.
rayryeng
+1 Esto también funciona para mí ... Entiendo que puede haber algunos riesgos de seguridad relacionados con este enfoque, por lo que los propietarios de las aplicaciones deben ser conscientes de ello.
LucasA
¿podría aclarar esta afirmación "Siempre es mejor cargar un recurso con getResourceAsStream ()"? ¿Cómo puede esto proporcionar una solución al problema?
Luca S.
@LucaS. Eso fue solo para el caso de las imágenes, lo siento, no estaba muy claro. El enfoque debería ser independiente de la plataforma, aunque es una forma un poco extraña.
Tombart
@LucasA ¿Podría aclarar los riesgos que mencionó?
ZX9
25

La respuesta de una línea es:

String path = this.getClass().getClassLoader().getResource(<resourceFileName>).toExternalForm()

Básicamente, el getResourcemétodo proporciona la URL. Desde esta URL puede extraer la ruta llamandotoExternalForm()

Referencias

getResource () , toExternalForm ()

Manojkumar Khotele
fuente
77
Mientras se ejecuta desde mi entorno (IntelliJ), esto produce una URL de archivo simple que funciona en todos los casos. Sin embargo, cuando ejecuto desde el propio jar, obtengo un URI similar a jar: file: /path/to/jar/jarname.jar! /File_in_jar.mp4. No todo puede utilizar un URI que comience con jar. Caso en punto JavaFX Media.
Noah Ternullo
1
Me gusta más esta respuesta. Por supuesto, en la mayoría de los casos puede ser preferible simplemente tomar InputStream al recurso cuando está en un archivo jar, pero si por alguna razón realmente necesita la ruta, esto es lo que funciona. Necesitaba el camino para dar a un objeto de terceros. ¡Así que gracias!
Mario
1
la solución anterior no funciona mientras está en IDE, en Intellijit se agrega file:/a la ruta, que funciona en jar pero no en IDE
Tayab Hussain el
Su respuesta resolvió un problema que había estado tratando de resolver durante al menos 2 días. TYVM !!
Jonathan
12

Pasé un tiempo jugando con este problema, porque ninguna solución que encontré realmente funcionó, ¡curiosamente! El directorio de trabajo con frecuencia no es el directorio del JAR, especialmente si se ejecuta un JAR (o cualquier otro programa) desde el menú Inicio en Windows. Así que esto es lo que hice, y funciona para archivos .class ejecutados desde fuera de un JAR tan bien como funciona para un JAR. (Solo lo probé en Windows 7.)

try {
    //Attempt to get the path of the actual JAR file, because the working directory is frequently not where the file is.
    //Example: file:/D:/all/Java/TitanWaterworks/TitanWaterworks-en.jar!/TitanWaterworks.class
    //Another example: /D:/all/Java/TitanWaterworks/TitanWaterworks.class
    PROGRAM_DIRECTORY = getClass().getClassLoader().getResource("TitanWaterworks.class").getPath(); // Gets the path of the class or jar.

    //Find the last ! and cut it off at that location. If this isn't being run from a jar, there is no !, so it'll cause an exception, which is fine.
    try {
        PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('!'));
    } catch (Exception e) { }

    //Find the last / and cut it off at that location.
    PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('/') + 1);
    //If it starts with /, cut it off.
    if (PROGRAM_DIRECTORY.startsWith("/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(1, PROGRAM_DIRECTORY.length());
    //If it starts with file:/, cut that off, too.
    if (PROGRAM_DIRECTORY.startsWith("file:/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(6, PROGRAM_DIRECTORY.length());
} catch (Exception e) {
    PROGRAM_DIRECTORY = ""; //Current working directory instead.
}
Desprogramador
fuente
8

si netclient.pestá dentro de un archivo JAR, no tendrá una ruta porque ese archivo se encuentra dentro de otro archivo. en ese caso, el mejor camino que puedes tener es realmente file:/path/to/jarfile/bot.jar!/config/netclient.p.

cd1
fuente
Cuando intento convertir una URL de este formato (... bot.jar! / Config / ...) a URI, dice que la ruta no es jerárquica.
gEdringer
7

Debe comprender la ruta dentro del archivo jar.
Simplemente consúltelo relativo. Entonces, si tiene un archivo (myfile.txt), ubicado en foo.jar bajo el \src\main\resourcesdirectorio (estilo maven). Te referirías a ella como:

src/main/resources/myfile.txt

Si vuelca su jar usando jar -tvf myjar.jar , verá la salida y la ruta relativa dentro del archivo jar, y usará SLASHES ADELANTE.

Eric manley
fuente
De hecho, debe usar barras diagonales, incluso en Windows. Eso implica que no puedes usar File.separator.
str
3

En mi caso, he usado un objeto URL en lugar de Path.

Expediente

File file = new File("my_path");
URL url = file.toURI().toURL();

Recurso en classpath usando classloader

URL url = MyClass.class.getClassLoader().getResource("resource_name")

Cuando necesito leer el contenido, puedo usar el siguiente código:

InputStream stream = url.openStream();

Y puede acceder al contenido utilizando un InputStream.

jmgoyesc
fuente
3

Este es el mismo código del usuario Tombart con flujo de flujo y cierre para evitar una copia incompleta del contenido del archivo temporal del recurso jar y tener nombres de archivo temporales únicos.

File file = null;
String resource = "/view/Trial_main.html" ;
URL res = getClass().getResource(resource);
if (res.toString().startsWith("jar:")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile(new Date().getTime()+"", ".html");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.flush();
        out.close();
        input.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}
         
Bruce
fuente
2

Un archivo es una abstracción para un archivo en un sistema de archivos, y los sistemas de archivos no saben nada sobre el contenido de un JAR.

Pruebe con un URI, creo que hay un protocolo jar: // que podría ser útil para sus propósitos.

fortran
fuente
1

El siguiente camino funcionó para mí: classpath:/path/to/resource/in/jar

Anatoly
fuente
1
@Anatoly ¿Podría compartir más datos sobre arriba
Kasun Siyambalapitiya
1
private static final String FILE_LOCATION = "com/input/file/somefile.txt";

//Method Body


InputStream invalidCharacterInputStream = URLClassLoader.getSystemResourceAsStream(FILE_LOCATION);

Obtener esto getSystemResourceAsStreames la mejor opción. Al obtener el flujo de entrada en lugar del archivo o la URL, funciona en un archivo JAR y de forma independiente.

Shano
fuente
1

Puede que sea un poco tarde, pero puede usar mi biblioteca KResourceLoader para obtener un recurso de su jar:

File resource = getResource("file.txt")
Lamberto Basti
fuente
0

Cuando se encuentra en un archivo jar, el recurso se encuentra absolutamente en la jerarquía del paquete (no en la jerarquía del sistema de archivos). Entonces, si tiene la clase com.example.Sweet cargando un recurso llamado "./default.conf", entonces el nombre del recurso se especifica como "/com/example/default.conf".

Pero si está en un frasco, entonces no es un archivo ...

Vilnis Krumins
fuente
0

Dentro de su carpeta de recursos (java / main / resources) de su jar agregue su archivo (asumimos que ha agregado un archivo xml llamado imports.xml ), luego inyecte ResourceLoadersi usa spring como abajo

@Autowired
private ResourceLoader resourceLoader;

dentro de la función de recorrido, escriba el siguiente código para cargar el archivo:

    Resource resource = resourceLoader.getResource("classpath:imports.xml");
    try{
        File file;
        file = resource.getFile();//will load the file
...
    }catch(IOException e){e.printStackTrace();}
BERGUIGA Mohamed Amine
fuente
0

Quizás este método pueda usarse para una solución rápida.

public class TestUtility
{ 
    public static File getInternalResource(String relativePath)
    {
        File resourceFile = null;
        URL location = TestUtility.class.getProtectionDomain().getCodeSource().getLocation();
        String codeLocation = location.toString();
        try{
            if (codeLocation.endsWith(".jar"){
                //Call from jar
                Path path = Paths.get(location.toURI()).resolve("../classes/" + relativePath).normalize();
                resourceFile = path.toFile();
            }else{
                //Call from IDE
                resourceFile = new File(TestUtility.class.getClassLoader().getResource(relativePath).getPath());
            }
        }catch(URISyntaxException ex){
            ex.printStackTrace();
        }
        return resourceFile;
    }
}
gbii
fuente
¿Estás omitiendo algún contexto? Esto me da:java.lang.NullPointerException: Attempt to invoke virtual method 'java.security.CodeSource java.security.ProtectionDomain.getCodeSource()' on a null object reference
Allen Luce
Edité la respuesta y escribí todo el método que utilicé en un software
gbii
0

sigue el código!

/ src / main / resources / file

streamToFile(getClass().getClassLoader().getResourceAsStream("file"))

public static File streamToFile(InputStream in) {
    if (in == null) {
        return null;
    }

    try {
        File f = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        f.deleteOnExit();

        FileOutputStream out = new FileOutputStream(f);
        byte[] buffer = new byte[1024];

        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }

        return f;
    } catch (IOException e) {
        LOGGER.error(e.getMessage(), e);
        return null;
    }
}
seunggabi
fuente