Verschiedene Möglichkeiten zum Laden einer Datei als InputStream

216

Was ist der Unterschied zwischen:

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

und

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

und

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

Wann ist jeder besser geeignet als die anderen?

Die Datei, die ich lesen möchte, befindet sich im Klassenpfad als meine Klasse, die die Datei liest. Meine Klasse und die Datei befinden sich im selben JAR und sind in einer EAR-Datei gepackt und in WebSphere 6.1 bereitgestellt.

zqudlyba
quelle

Antworten:

289

Es gibt subtile Unterschiede, wie das, was fileNameSie passieren, interpretiert wird. Grundsätzlich haben Sie zwei verschiedene Methoden: ClassLoader.getResourceAsStream()undClass.getResourceAsStream() . Diese beiden Methoden lokalisieren die Ressource unterschiedlich.

In Class.getResourceAsStream(path)wird der Pfad als lokaler Pfad für das Paket der Klasse interpretiert, von der aus Sie ihn aufrufen. Wenn Sie beispielsweise aufrufen, String.getResourceAsStream("myfile.txt")suchen Sie in Ihrem Klassenpfad am folgenden Speicherort nach einer Datei : "java/lang/myfile.txt". Wenn Ihr Pfad mit a beginnt /, wird er als absoluter Pfad betrachtet und beginnt mit der Suche im Stammverzeichnis des Klassenpfads. Wenn Sie anrufen, String.getResourceAsStream("/myfile.txt")wird der folgende Ort in Ihrem Klassenpfad angezeigt ./myfile.txt.

ClassLoader.getResourceAsStream(path)betrachtet alle Pfade als absolute Pfade. Rufen Sie also an String.getClassLoader().getResourceAsStream("myfile.txt")und String.getClassLoader().getResourceAsStream("/myfile.txt")suchen beide nach einer Datei in Ihrem Klassenpfad an folgendem Speicherort : ./myfile.txt.

Jedes Mal, wenn ich in diesem Beitrag einen Speicherort erwähne, kann dies ein Speicherort in Ihrem Dateisystem selbst oder in der entsprechenden JAR-Datei sein, abhängig von der Klasse und / oder dem ClassLoader, aus dem Sie die Ressource laden.

In Ihrem Fall laden Sie die Klasse von einem Anwendungsserver, daher sollten Sie Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)stattdessen verwenden this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()wird auch funktionieren.

In diesem Artikel finden Sie detailliertere Informationen zu diesem bestimmten Problem.


Warnung für Benutzer von Tomcat 7 und niedriger

Eine der Antworten auf diese Frage besagt, dass meine Erklärung für Tomcat 7 falsch zu sein scheint. Ich habe versucht, mich umzuschauen, um herauszufinden, warum dies der Fall ist.

Daher habe ich mir den Quellcode von Tomcat WebAppClassLoaderfür mehrere Versionen von Tomcat angesehen. Die Implementierung von findResource(String name)(die maßgeblich für die Erstellung der URL zur angeforderten Ressource verantwortlich ist) ist in Tomcat 6 und Tomcat 7 praktisch identisch, in Tomcat 8 jedoch unterschiedlich.

In den Versionen 6 und 7 versucht die Implementierung nicht, den Ressourcennamen zu normalisieren. Dies bedeutet, dass in diesen Versionen classLoader.getResourceAsStream("/resource.txt")möglicherweise nicht das gleiche Ergebnis wie beim classLoader.getResourceAsStream("resource.txt")Ereignis erzielt wird (obwohl dies vom Javadoc angegeben wird). [Quellcode]

In Version 8 wird der Ressourcenname jedoch normalisiert, um sicherzustellen, dass die absolute Version des Ressourcennamens verwendet wird. Daher sollten in Tomcat 8 die beiden oben beschriebenen Aufrufe immer das gleiche Ergebnis liefern.[Quellcode]

Aus diesem Grund müssen Sie besonders vorsichtig sein, wenn Sie Tomcat-Versionen vor 8 verwenden ClassLoader.getResourceAsStream()oder Class.getResourceAsStream()verwenden. Außerdem müssen Sie berücksichtigen, dass class.getResourceAsStream("/resource.txt")tatsächlich Anrufe getätigt werden classLoader.getResourceAsStream("resource.txt")(der führende /wird entfernt).

LordOfThePigs
quelle
2
Ich bin mir ziemlich sicher, dass getClass().getResourceAsStream("/myfile.txt")sich das anders verhält als getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon
@ BrianGordon: Sie verhalten sich nicht anders. Tatsächlich sagt das Javadoc für Class.getResourceAsStream (String) Folgendes aus: "Diese Methode delegiert an den Klassenlader dieses Objekts." Und gibt dann eine Reihe von Regeln an, wie ein relativer Pfad in einen absoluten Pfad konvertiert wird, bevor an den delegiert wird Klassenlader.
LordOfThePigs
@LordOfThePigs Sehen Sie sich die aktuelle Quelle an. Class.getResourceAsStream entfernt den führenden Schrägstrich, wenn Sie einen absoluten Pfad angeben.
Brian Gordon
4
@BrianGordon: Dadurch verhält es sich genauso wie ClassLoader.getResourceAsStream (), da letzterer alle Pfade als absolut interpretiert, unabhängig davon, ob sie mit einem führenden Schrägstrich beginnen oder nicht. Solange Ihr Pfad absolut ist, verhalten sich beide Methoden identisch. Wenn Ihr Pfad relativ ist, ist das Verhalten anders.
LordOfThePigs
Ich konnte nicht finden getClassLoader()von String, ist es ein Fehler , oder benötigen eine Erweiterung?
AaA
21

Verwenden Sie MyClass.class.getClassLoader().getResourceAsStream(path)diese Option , um die mit Ihrem Code verknüpfte Ressource zu laden. Verwenden Sie diese Option MyClass.class.getResourceAsStream(path)als Verknüpfung und für Ressourcen, die im Paket Ihrer Klasse enthalten sind.

Verwenden Sie Thread.currentThread().getContextClassLoader().getResourceAsStream(path)diese Option , um Ressourcen abzurufen, die Teil des Clientcodes sind und nicht eng an den aufrufenden Code gebunden sind. Sie sollten damit vorsichtig sein, da der Thread-Kontextklassenlader auf alles zeigen kann.

Tom Hawtin - Tackline
quelle
6

Einfaches altes Java auf einfachem altem Java 7 und keine anderen Abhängigkeiten zeigen den Unterschied ...

Ich legte file.txtein c:\temp\und legte c:\temp\den Klassenpfad an.

Es gibt nur einen Fall, in dem es einen Unterschied zwischen den beiden Anrufen gibt.

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
quelle
Vielen Dank, für mich hat nur 'J.class.getResourceAsStream ("file.txt")' gearbeitet
abbasalim
3

Alle diese Antworten hier sowie die Antworten in dieser Frage legen nahe, dass das Laden absoluter URLs wie "/foo/bar.properties" von class.getResourceAsStream(String)und gleich behandelt wird class.getClassLoader().getResourceAsStream(String). Dies ist NICHT der Fall, zumindest nicht in meiner Tomcat-Konfiguration / Version (derzeit 7.0.40).

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

Entschuldigung, ich habe absolut keine befriedigende Erklärung, aber ich denke, dass Tomcat schmutzige Tricks und seine schwarze Magie mit den Klassenladern macht und den Unterschied verursacht. Ich habe es class.getResourceAsStream(String)in der Vergangenheit immer benutzt und hatte keine Probleme.

PS: Ich stellte auch dies über hier

Tim Büthe
quelle
Vielleicht beschließt Tomcat, die Spezifikation nicht zu respektieren und behandelt nicht alle übergebenen Pfade ClassLoader.getResourceAsStream()als absolut? Dies ist plausibel, da, wie in einigen Kommentaren oben erwähnt, Class.getResourceAsStreamtatsächlich getClassLoader (). GetResourceAsStream` aufgerufen wird, aber jeder führende Schrägstrich entfernt wird.
LordOfThePigs
Nachdem ich den Quellcode von Java SE eingecheckt habe, denke ich, dass ich die Antwort habe: Beides Class.getResourceAsStream()und ClassLoader.getResourceAsStream()letztendlich Aufruf ClassLoader.findResource()einer geschützten Methode, deren Standardimplementierung leer ist, deren Javadoc jedoch ausdrücklich angibt: "Klassenladeprogrammimplementierungen sollten diese Methode überschreiben, um anzugeben, wo Ressourcen finden ". Ich vermute, dass die Implementierung dieser speziellen Methode durch Tomcat fehlerhaft sein könnte.
LordOfThePigs
Ich habe auch die Umsetzung der im Vergleich WebAppClassLoader.findResource(String name)in Tomcat 7 mit dem von Tomcat 8 , und es scheint , dass es ein entscheidender Unterschied gibt. Tomcat 8 normalisiert den Ressourcennamen explizit, indem es einen führenden Namen hinzufügt, /wenn dieser keinen enthält, wodurch alle Namen absolut werden. Tomcat 7 nicht. Das ist eindeutig ein Fehler in Tomcat 7
LordOfThePigs
Ich habe in meiner Antwort einen Absatz dazu hinzugefügt.
LordOfThePigs
0

Nachdem ich einige Möglichkeiten ausprobiert hatte, die Datei ohne Erfolg zu laden, erinnerte ich mich, dass ich sie verwenden konnte FileInputStream, was perfekt funktionierte.

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

Dies ist eine weitere Möglichkeit, eine Datei in eine Datei einzulesen. InputStreamSie liest die Datei aus dem aktuell ausgeführten Ordner.

António Almeida
quelle
Es ist keine Datei, es ist eine Ressource. Die Antwort ist nicht richtig.
Marquis von Lorne
1
@EJP Ich lande in dieser SO-Antwort und suche nach Möglichkeiten, eine Datei zu laden, ohne den Unterschied zwischen einer Datei und einer Ressource zu kennen. Ich werde meine Antwort nicht löschen, da dies anderen helfen kann.
António Almeida
-3

Es funktioniert, probieren Sie Folgendes aus:

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