Forma preferida de cargar recursos en Java

107

Me gustaría saber la mejor forma de cargar un recurso en Java:

  • this.getClass().getResource() (or getResourceAsStream()),
  • Thread.currentThread().getContextClassLoader().getResource(name),
  • System.class.getResource(name).
Doc Davluz
fuente

Respuestas:

140

Trabaja la solución de acuerdo a lo que quieras ...

Hay dos cosas que getResource/ getResourceAsStream()se obtiene de la clase que se llama en ...

  1. El cargador de clases
  2. La ubicación de partida

Así que si lo haces

this.getClass().getResource("foo.txt");

intentará cargar foo.txt desde el mismo paquete que la clase "this" y con el cargador de clases de la clase "this". Si coloca una "/" al principio, está haciendo referencia absoluta al recurso.

this.getClass().getResource("/x/y/z/foo.txt")

cargará el recurso desde el cargador de clases de "this" y desde el paquete xyz (deberá estar en el mismo directorio que las clases en ese paquete).

Thread.currentThread().getContextClassLoader().getResource(name)

se cargará con el cargador de clases de contexto pero no resolverá el nombre de acuerdo con ningún paquete (debe estar absolutamente referenciado)

System.class.getResource(name)

Cargará el recurso con el cargador de clases del sistema (también tendría que estar absolutamente referenciado, ya que no podrá poner nada en el paquete java.lang (el paquete de System).

Solo eche un vistazo a la fuente. También indica que getResourceAsStream solo llama a "openStream" en la URL devuelta por getResource y lo devuelve.

Michael Wiles
fuente
Los nombres de los paquetes AFAIK no importan, lo que importa es la ruta de clase del cargador de clases.
Bart van Heukelom
@Bart, si miras el código fuente, notarás que el nombre de la clase sí importa cuando llamas a getResource en una clase. Lo primero que hace esta llamada es llamar a "resolveName", que agrega el prefijo del paquete si corresponde. Javadoc para resolveName es "Agregar un prefijo de nombre de paquete si el nombre no es absoluto Quitar" / "inicial si el nombre es absoluto"
Michael Wiles
10
Ah, ya veo. Absoluto aquí significa relativo a la ruta de clases, en lugar de absoluto del sistema de archivos.
Bart van Heukelom
1
Solo quiero agregar que siempre debe verificar que la secuencia devuelta por getResourceAsStream () no sea nula, porque lo será si el recurso no está dentro de la ruta de clase.
stenix
También vale la pena señalar que el uso del cargador de clases de contexto permite cambiar el cargador de clases en tiempo de ejecución a través de Thread#setContextClassLoader. Esto es útil si necesita modificar la ruta de clases mientras se ejecuta el programa.
Máximo
14

Bueno, depende en parte de lo que quieras que suceda si realmente estás en una clase derivada.

Por ejemplo, supongamos que SuperClassestá en A.jar y SubClassestá en B.jar, y está ejecutando código en un método de instancia declarado en SuperClasspero donde se thisrefiere a una instancia de SubClass. Si lo usa this.getClass().getResource(), se verá relativo a SubClass, en B.jar. Sospecho que normalmente eso no es lo que se requiere.

Personalmente, probablemente lo usaría con Foo.class.getResourceAsStream(name)más frecuencia: si ya conoce el nombre del recurso que está buscando y está seguro de dónde está en relación Foo, esa es la forma más sólida de hacerlo, en mi opinión.

Por supuesto, hay ocasiones en las que eso no es lo que quieres también: juzga cada caso por sus méritos. Es simplemente el "Sé que este recurso está incluido con esta clase" es el más común con el que me he encontrado.

Jon Skeet
fuente
skeet: una duda en la declaración "está ejecutando código en un método de instancia de SuperClass pero donde esto se refiere a una instancia de SubClass" si estamos ejecutando declaraciones dentro del método de instancia de superclase, entonces "this" se referirá a la superclase no la subclase.
Programador muerto
1
@Suresh: No, no lo hará. ¡Intentalo! Cree dos clases, haciendo que una se derive de la otra, y luego imprima en la superclase this.getClass(). Cree una instancia de la subclase y llame al método ... imprimirá el nombre de la subclase, no la superclase.
Jon Skeet
gracias método de instancia de subclase llama al método de superclase.
Programador muerto
1
Lo que me pregunto es si el uso de this.getResourceAsStream solo podrá cargar un recurso desde el mismo jar desde el que es esta clase y no desde otro jar. Según mis cálculos, ¿es el cargador de clases el que carga el recurso y seguramente no se limitará a cargar desde un solo frasco?
Michael Wiles
10

Busco tres lugares como se muestra a continuación. Comentarios bienvenidos.

public URL getResource(String resource){

    URL url ;

    //Try with the Thread Context Loader. 
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if(classLoader != null){
        url = classLoader.getResource(resource);
        if(url != null){
            return url;
        }
    }

    //Let's now try with the classloader that loaded this class.
    classLoader = Loader.class.getClassLoader();
    if(classLoader != null){
        url = classLoader.getResource(resource);
        if(url != null){
            return url;
        }
    }

    //Last ditch attempt. Get the resource from the classpath.
    return ClassLoader.getSystemResource(resource);
}
dogbane
fuente
Gracias, esta es una gran idea. Justo lo que necesitaba.
devo
2
Estaba mirando los comentarios en su código y el último suena interesante. ¿No se cargan todos los recursos desde el classpath? ¿Y qué casos cubriría ClassLoader.getSystemResource () en los que lo anterior no tuvo éxito?
nyxz
Honestamente, no entiendo por qué querría cargar archivos desde 3 lugares diferentes. ¿No sabes dónde se almacenan tus archivos?
bvdb
3

Sé que es muy tarde para otra respuesta, pero solo quería compartir lo que me ayudó al final. También cargará recursos / archivos desde la ruta absoluta del sistema de archivos (no solo la ruta de clases).

public class ResourceLoader {

    public static URL getResource(String resource) {
        final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
        classLoaders.add(Thread.currentThread().getContextClassLoader());
        classLoaders.add(ResourceLoader.class.getClassLoader());

        for (ClassLoader classLoader : classLoaders) {
            final URL url = getResourceWith(classLoader, resource);
            if (url != null) {
                return url;
            }
        }

        final URL systemResource = ClassLoader.getSystemResource(resource);
        if (systemResource != null) {
            return systemResource;
        } else {
            try {
                return new File(resource).toURI().toURL();
            } catch (MalformedURLException e) {
                return null;
            }
        }
    }

    private static URL getResourceWith(ClassLoader classLoader, String resource) {
        if (classLoader != null) {
            return classLoader.getResource(resource);
        }
        return null;
    }

}
nyxz
fuente
0

Probé muchas formas y funciones que se sugirieron anteriormente, pero no funcionaron en mi proyecto. De todos modos he encontrado una solución y aquí está:

try {
    InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
    img = ImageIO.read(path);
} catch (IOException e) {
    e.printStackTrace();
}
Vladislav
fuente
Es mejor usarlo this.getClass().getResourceAsStream()en este caso. Si echa un vistazo a la fuente del getResourceAsStreammétodo, notará que hace lo mismo que usted, pero de una manera más inteligente (alternativa si no ClassLoaderse puede encontrar en la clase). También indica que puede encontrarse con un potencial nullen getClassLoadersu código ...
Doc Davluz
@PromCompot, como dije, this.getClass().getResourceAsStream()no me funciona, así que lo uso. Creo que hay algunas personas que pueden afrontar un problema como el mío.
Vladislav