Diferentes formas de cargar un archivo como InputStream

216

Cuál es la diferencia entre:

InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName)

y

InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)

y

InputStream is = this.getClass().getResourceAsStream(fileName)

¿Cuándo son cada uno más apropiado para usar que los demás?

El archivo que quiero leer está en el classpath como mi clase que lee el archivo. Mi clase y el archivo están en el mismo jar, empaquetados en un archivo EAR y desplegados en WebSphere 6.1.

zqudlyba
fuente

Respuestas:

289

Hay diferencias sutiles en cuanto a cómo fileNamese interpreta lo que está pasando. Básicamente, tiene 2 métodos diferentes: ClassLoader.getResourceAsStream()y Class.getResourceAsStream(). Estos dos métodos ubicarán el recurso de manera diferente.

En Class.getResourceAsStream(path), la ruta se interpreta como una ruta local al paquete de la clase desde la que lo está llamando. Por ejemplo vocación, String.getResourceAsStream("myfile.txt")buscará un archivo en la ruta de clases en la siguiente dirección: "java/lang/myfile.txt". Si su ruta comienza con a /, se considerará una ruta absoluta y comenzará a buscar desde la raíz de la ruta de clase. Por lo tanto, al llamar String.getResourceAsStream("/myfile.txt")verá la siguiente ubicación en su ruta de clase ./myfile.txt.

ClassLoader.getResourceAsStream(path)considerará todas las rutas como rutas absolutas. Así que llamar String.getClassLoader().getResourceAsStream("myfile.txt")y String.getClassLoader().getResourceAsStream("/myfile.txt")lo hará tanto buscar un archivo en la ruta de clases en la siguiente dirección: ./myfile.txt.

Cada vez que menciono una ubicación en esta publicación, podría ser una ubicación en su propio sistema de archivos, o dentro del archivo jar correspondiente, dependiendo de la Clase y / o el Cargador de clases desde el que está cargando el recurso.

En su caso, está cargando la clase desde un Servidor de aplicaciones, por lo que debe usarla en Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)lugar de this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()También funcionará.

Lea este artículo para obtener información más detallada sobre ese problema en particular.


Advertencia para usuarios de Tomcat 7 y versiones inferiores

Una de las respuestas a esta pregunta dice que mi explicación parece ser incorrecta para Tomcat 7. He intentado mirar a mi alrededor para ver por qué ese sería el caso.

Así que he visto el código fuente de Tomcat WebAppClassLoaderpara varias versiones de Tomcat. La implementación de findResource(String name)(que es totalmente responsable de producir la URL del recurso solicitado) es prácticamente idéntica en Tomcat 6 y Tomcat 7, pero es diferente en Tomcat 8.

En las versiones 6 y 7, la implementación no intenta normalizar el nombre del recurso. Esto significa que en estas versiones, classLoader.getResourceAsStream("/resource.txt")puede que no produzca el mismo resultado que el classLoader.getResourceAsStream("resource.txt")evento aunque debería (ya que eso es lo que especifica el Javadoc). [código fuente]

Sin embargo, en la versión 8, el nombre del recurso se normaliza para garantizar que la versión absoluta del nombre del recurso es la que se utiliza. Por lo tanto, en Tomcat 8, las dos llamadas descritas anteriormente siempre deben devolver el mismo resultado. [código fuente]

Como resultado, debe tener mucho cuidado al usar ClassLoader.getResourceAsStream()o Class.getResourceAsStream()en versiones de Tomcat anteriores a 8. Y también debe tener en cuenta que class.getResourceAsStream("/resource.txt")realmente llama classLoader.getResourceAsStream("resource.txt")( /se despoja el inicio).

LordOfThePigs
fuente
2
Estoy bastante seguro de que se getClass().getResourceAsStream("/myfile.txt")comporta de manera diferente getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon
@BrianGordon: No se comportan de manera diferente. En realidad, el javadoc para Class.getResourceAsStream (String) dice lo siguiente: "Este método delega al cargador de clases de este objeto", y luego da un montón de reglas sobre cómo convierte una ruta relativa en una ruta absoluta antes de delegar en el cargador de clases
LordOfThePigs
@LordOfThePigs Mira la fuente real. Class.getResourceAsStream elimina la barra diagonal inicial si proporciona una ruta absoluta.
Brian Gordon
44
@BrianGordon: lo que hace que se comporte exactamente igual que ClassLoader.getResourceAsStream () ya que este último interpreta todas las rutas como absolutas, ya sea que comiencen con una barra diagonal o no. Entonces, siempre que su ruta sea absoluta, ambos métodos se comportan de manera idéntica. Si su camino es relativo, entonces el comportamiento es diferente.
LordOfThePigs
No pude encontrar getClassLoader()de String, es un error o necesita una extensión?
AaA
21

Úselo MyClass.class.getClassLoader().getResourceAsStream(path)para cargar recursos asociados con su código. Úselo MyClass.class.getResourceAsStream(path)como acceso directo y para recursos empaquetados dentro del paquete de su clase.

Úselo Thread.currentThread().getContextClassLoader().getResourceAsStream(path)para obtener recursos que son parte del código del cliente, que no están estrechamente vinculados al código de llamada. Debe tener cuidado con esto, ya que el cargador de clases de contexto de subprocesos podría apuntar a cualquier cosa.

Tom Hawtin - tackline
fuente
6

Java simple en Java 7 simple y ninguna otra dependencia demuestra la diferencia ...

Pongo file.txten c:\temp\y puse c:\temp\en la ruta de clase.

Solo hay un caso en el que hay una diferencia entre las dos llamadas.

class J {

 public static void main(String[] a) {
    // as "absolute"

    // ok   
    System.err.println(J.class.getResourceAsStream("/file.txt") != null); 

    // pop            
    System.err.println(J.class.getClassLoader().getResourceAsStream("/file.txt") != null); 

    // as relative

    // ok
    System.err.println(J.class.getResourceAsStream("./file.txt") != null); 

    // ok
    System.err.println(J.class.getClassLoader().getResourceAsStream("./file.txt") != null); 

    // no path

    // ok
    System.err.println(J.class.getResourceAsStream("file.txt") != null); 

   // ok
   System.err.println(J.class.getClassLoader().getResourceAsStream("file.txt") != null); 
  }
}
John Lonergan
fuente
muchas gracias, para mí solo funcionó 'J.class.getResourceAsStream ("file.txt")'
abbasalim
3

Todas estas respuestas por aquí, así como las respuestas en esta pregunta , sugieren que la carga de URL absolutas, como "/foo/bar.properties", se trata de la misma manera por class.getResourceAsStream(String)y class.getClassLoader().getResourceAsStream(String). Este NO es el caso, al menos no en mi configuración / versión de Tomcat (actualmente 7.0.40).

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

Lo siento, no tengo ninguna explicación satisfactoria, pero supongo que Tomcat hace trucos sucios y su magia negra con los cargadores de clases y causa la diferencia. Siempre lo usé class.getResourceAsStream(String)en el pasado y no he tenido ningún problema.

PD: también publiqué esto aquí

Tim Büthe
fuente
¿Quizás Tomcat decide no respetar la especificación y no trata todas las rutas pasadas ClassLoader.getResourceAsStream()como absolutas? Esto es plausible porque, como se mencionó en algunos comentarios anteriores, en Class.getResourceAsStreamrealidad llama a getClassLoader (). GetResourceAsStream` pero elimina cualquier barra diagonal inicial.
LordOfThePigs
Después de verificar el código fuente de Java SE, creo que tengo la respuesta: Ambos Class.getResourceAsStream()y ClassLoader.getResourceAsStream()finalmente terminan llamando, ClassLoader.findResource()que es un método protegido cuya implementación predeterminada está vacía, pero cuyo javadoc declara explícitamente "Las implementaciones del cargador de clases deberían anular este método para especificar dónde para encontrar recursos ". Sospecho que la implementación de Tomcat de este método en particular puede ser defectuosa.
LordOfThePigs
También he comparado la implementación de WebAppClassLoader.findResource(String name)en Tomcat 7 con la de Tomcat 8 , y parece que hay una diferencia clave. Tomcat 8 normaliza explícitamente el nombre del recurso agregando un encabezado /si no contiene ninguno, lo que hace que todos los nombres sean absolutos. Tomcat 7 no. Eso es claramente un error en Tomcat 7
LordOfThePigs
Agregué un párrafo sobre eso en mi respuesta.
LordOfThePigs
0

Después de intentar algunas formas de cargar el archivo sin éxito, recordé que podía usarlo FileInputStream, lo que funcionó perfectamente.

InputStream is = new FileInputStream("file.txt");

Esta es otra forma de leer un archivo en un archivo InputStream, lee el archivo de la carpeta actualmente en ejecución.

António Almeida
fuente
No es un archivo, es un recurso. La respuesta no es correcta.
Marqués de Lorne
1
@EJP Termino en esta respuesta SO, buscando formas de cargar un archivo, sin saber la diferencia entre un archivo y un recurso. No voy a eliminar mi respuesta porque puede ayudar a otros.
António Almeida
-3

Funciona, prueba esto:

InputStream in_s1 =   TopBrandData.class.getResourceAsStream("/assets/TopBrands.xml");
Jaspreet Singh
fuente