Différentes façons de charger un fichier en tant que InputStream

216

Quelle est la différence entre:

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

et

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

et

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

Quand est-il plus approprié d'utiliser chacun que les autres?

Le fichier que je veux lire est dans le chemin de classe comme ma classe qui lit le fichier. Ma classe et le fichier sont dans le même pot et empaquetés dans un fichier EAR et déployés dans WebSphere 6.1.

zqudlyba
la source

Réponses:

289

Il existe des différences subtiles quant à la façon dont fileNamevous passez est interprété. Fondamentalement, vous avez 2 méthodes différentes: ClassLoader.getResourceAsStream()etClass.getResourceAsStream() . Ces deux méthodes localiseront la ressource différemment.

Dans Class.getResourceAsStream(path), le chemin d'accès est interprété comme un chemin d'accès local au package de la classe à partir de laquelle vous l'appelez. Par exemple appel, String.getResourceAsStream("myfile.txt")va chercher un fichier dans votre classpath à l'adresse suivante: "java/lang/myfile.txt". Si votre chemin commence par un /, il sera alors considéré comme un chemin absolu et commencera la recherche à partir de la racine du chemin de classe. Ainsi, l'appel String.getResourceAsStream("/myfile.txt")se penchera sur l'emplacement suivant dans votre chemin de classe ./myfile.txt.

ClassLoader.getResourceAsStream(path)considérera tous les chemins comme des chemins absolus. Donc , appeler String.getClassLoader().getResourceAsStream("myfile.txt")et String.getClassLoader().getResourceAsStream("/myfile.txt")sera à la fois regarder un fichier dans votre classpath à l'adresse suivante: ./myfile.txt.

Chaque fois que je mentionne un emplacement dans cet article, il peut s'agir d'un emplacement dans votre système de fichiers lui-même, ou à l'intérieur du fichier jar correspondant, en fonction de la classe et / ou du ClassLoader à partir desquels vous chargez la ressource.

Dans votre cas, vous chargez la classe à partir d'un serveur d'applications, vous devez donc l'utiliser à la Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)place de this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()fonctionnera également.

Lisez cet article pour des informations plus détaillées sur ce problème particulier.


Avertissement pour les utilisateurs de Tomcat 7 et inférieurs

L'une des réponses à cette question indique que mon explication semble incorrecte pour Tomcat 7. J'ai essayé de regarder autour de moi pour voir pourquoi ce serait le cas.

J'ai donc regardé le code source de Tomcat WebAppClassLoaderpour plusieurs versions de Tomcat. L'implémentation de findResource(String name)(qui est ultimement responsable de la production de l'URL de la ressource demandée) est pratiquement identique dans Tomcat 6 et Tomcat 7, mais est différente dans Tomcat 8.

Dans les versions 6 et 7, l'implémentation n'essaie pas de normaliser le nom de la ressource. Cela signifie que dans ces versions, il se classLoader.getResourceAsStream("/resource.txt")peut qu'il ne produise pas le même résultat que l' classLoader.getResourceAsStream("resource.txt")événement (car c'est ce que spécifie le Javadoc). [code source]

Cependant, dans la version 8, le nom de la ressource est normalisé pour garantir que la version absolue du nom de la ressource est celle utilisée. Par conséquent, dans Tomcat 8, les deux appels décrits ci-dessus doivent toujours renvoyer le même résultat. [code source]

Par conséquent, vous devez être extrêmement prudent lorsque vous utilisez ClassLoader.getResourceAsStream()ou Class.getResourceAsStream()sur des versions de Tomcat antérieures à 8. Et vous devez également garder à l'esprit que les class.getResourceAsStream("/resource.txt")appels sont réellement effectués classLoader.getResourceAsStream("resource.txt")(le début /est supprimé).

LordOfThePigs
la source
2
Je suis presque sûr que cela getClass().getResourceAsStream("/myfile.txt")se comporte différemment de getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon
@BrianGordon: Ils ne se comportent pas différemment. En fait, le javadoc pour Class.getResourceAsStream (String) dit la chose suivante: "Cette méthode délègue au chargeur de classe de cet objet.", Puis donne un tas de règles sur la façon dont il convertit un chemin relatif en chemin absolu avant de déléguer à la chargeur de classe.
LordOfThePigs
@LordOfThePigs Regardez la source réelle. Class.getResourceAsStream supprime la barre oblique avant si vous fournissez un chemin absolu.
Brian Gordon
4
@BrianGordon: ce qui fait qu'il se comporte exactement de la même manière que ClassLoader.getResourceAsStream () car ce dernier interprète tous les chemins comme absolus, qu'ils commencent ou non par une barre oblique. Donc, tant que votre chemin est absolu, les deux méthodes se comportent de manière identique. Si votre chemin est relatif, le comportement est différent.
LordOfThePigs
Je ne pouvais pas trouver getClassLoader()de String, est - ce une erreur ou besoin d' une extension?
AaA
21

Utilisez MyClass.class.getClassLoader().getResourceAsStream(path)pour charger la ressource associée à votre code. UtilisationMyClass.class.getResourceAsStream(path) comme raccourci et pour les ressources regroupées dans le package de votre classe.

À utiliser Thread.currentThread().getContextClassLoader().getResourceAsStream(path)pour obtenir des ressources qui font partie du code client, et non pas étroitement liées au code appelant. Vous devez être prudent avec cela car le chargeur de classe de contexte de thread peut pointer vers n'importe quoi.

Tom Hawtin - sellerie
la source
6

Plain old Java on plain old Java 7 et aucune autre dépendance ne démontre la différence ...

Je mets file.txten c:\temp\et je mets c:\temp\sur le chemin de classe.

Il n'y a qu'un seul cas où il y a une différence entre les deux appels.

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
la source
merci beaucoup, pour moi, j'ai seulement travaillé 'J.class.getResourceAsStream ("file.txt")'
abbasalim
3

Toutes ces réponses ici, ainsi que les réponses à cette question , suggèrent que le chargement des URL absolues, comme "/foo/bar.properties", était traité de la même manière par class.getResourceAsStream(String)et class.getClassLoader().getResourceAsStream(String). Ce n'est PAS le cas, du moins pas dans ma configuration / version Tomcat (actuellement 7.0.40).

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

Désolé, je n'ai absolument aucune explication satisfaisante, mais je suppose que Tomcat fait des tours sales et sa magie noire avec les chargeurs de classe et cause la différence. J'ai toujours utilisé class.getResourceAsStream(String)dans le passé et je n'ai eu aucun problème.

PS: j'ai aussi posté ça ici

Tim Büthe
la source
Peut-être que Tomcat décide de ne pas respecter la spécification et ne traite pas tous les chemins passés ClassLoader.getResourceAsStream()comme absolus? Ceci est plausible car, comme mentionné dans certains commentaires ci-dessus, Class.getResourceAsStreamappelle en fait getClassLoader (). GetResourceAsStream` mais supprime toute barre oblique de début.
LordOfThePigs
Après avoir vérifié dans le code source de Java SE, je pense que je tiens la réponse: les deux Class.getResourceAsStream()et ClassLoader.getResourceAsStream()finalement finir par appeler ce ClassLoader.findResource()qui est une méthode protégée dont la mise en œuvre par défaut est vide, mais dont javadoc déclare explicitement « implémentations de classe chargeur doivent remplacer cette méthode pour spécifier où trouver des ressources ". Je soupçonne que l'implémentation par tomcat de cette méthode particulière peut être défectueuse.
LordOfThePigs
J'ai également comparé la mise en œuvre de WebAppClassLoader.findResource(String name)dans Tomcat 7 avec celui de Tomcat 8 , et il semble qu'il ya une différence essentielle. Tomcat 8 normalise explicitement le nom de la ressource en ajoutant un interligne /s'il n'en contient pas, ce qui rend tous les noms absolus. Tomcat 7 ne le fait pas. C'est clairement un bug dans Tomcat 7
LordOfThePigs
J'ai ajouté un paragraphe à ce sujet dans ma réponse.
LordOfThePigs
0

Après avoir essayé quelques façons de charger le fichier sans succès, je me suis souvenu que je pouvais l'utiliser FileInputStream, ce qui fonctionnait parfaitement.

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

C'est une autre façon de lire un fichier dans un InputStream, il lit le fichier à partir du dossier en cours d'exécution.

António Almeida
la source
Ce n'est pas un fichier, c'est une ressource. La réponse n'est pas correcte.
Marquis de Lorne le
1
@EJP Je me retrouve dans cette réponse SO, cherchant des moyens de charger un fichier, sans connaître la différence entre un fichier et une ressource. Je ne vais pas supprimer ma réponse car cela peut aider les autres.
António Almeida
-3

Ça marche, essayez ceci:

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