Różne sposoby ładowania pliku jako InputStream

216

Jaka jest różnica pomiędzy:

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

i

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

i

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

Kiedy każdy z nich jest bardziej odpowiedni do użycia niż inne?

Plik, który chcę przeczytać, znajduje się w ścieżce klasy, tak jak moja klasa, która czyta plik. Moja klasa i plik znajdują się w tym samym słoiku i są spakowane w pliku EAR i wdrożone w WebSphere 6.1.

zqudlyba
źródło

Odpowiedzi:

289

Istnieją subtelne różnice w fileNameinterpretacji tego, co mijasz. Zasadniczo masz 2 różne metody: ClassLoader.getResourceAsStream()i Class.getResourceAsStream(). Te dwie metody będą lokalizować zasób inaczej.

W Class.getResourceAsStream(path), ścieżka jest interpretowana jako lokalna ścieżka do pakietu klasy, z której ją wywołujesz. Na przykład powołania, String.getResourceAsStream("myfile.txt")będzie szukał pliku w ścieżce klas w następującej lokalizacji: "java/lang/myfile.txt". Jeśli twoja ścieżka zaczyna się od a /, zostanie uznana za ścieżkę bezwzględną i rozpocznie wyszukiwanie od źródła ścieżki klasy. W ten sposób wywołanie String.getResourceAsStream("/myfile.txt")obejrzy następującą lokalizację na ścieżce zajęć ./myfile.txt.

ClassLoader.getResourceAsStream(path)weźmie wszystkie ścieżki za ścieżki absolutne. Więc dzwoni String.getClassLoader().getResourceAsStream("myfile.txt")i String.getClassLoader().getResourceAsStream("/myfile.txt")będzie zarówno wygląd pliku w ścieżce klas w następującej lokalizacji: ./myfile.txt.

Za każdym razem, gdy wspominam o lokalizacji w tym poście, może to być lokalizacja w twoim systemie plików lub wewnątrz odpowiedniego pliku jar, w zależności od Class i / lub ClassLoadera, z którego ładujesz zasób.

W twoim przypadku ładujesz klasę z serwera aplikacji, więc powinieneś użyć Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)zamiast this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()będzie również działać.

Przeczytaj ten artykuł, aby uzyskać bardziej szczegółowe informacje na temat tego konkretnego problemu.


Ostrzeżenie dla użytkowników Tomcat 7 i niższych

Jedna z odpowiedzi na to pytanie mówi, że moje wyjaśnienie wydaje się niepoprawne w przypadku Tomcat 7. Próbowałem się rozejrzeć, aby zobaczyć, dlaczego tak się dzieje.

Więc spojrzałem na kod źródłowy Tomcata WebAppClassLoaderdla kilku wersji Tomcata. Implementacja findResource(String name)(która jest całkowicie odpowiedzialna za tworzenie adresu URL do żądanego zasobu) jest praktycznie identyczna w Tomcat 6 i Tomcat 7, ale jest inna w Tomcat 8.

W wersjach 6 i 7 implementacja nie próbuje znormalizować nazwy zasobu. Oznacza to, że w tych wersjach classLoader.getResourceAsStream("/resource.txt")może nie dawać tego samego wyniku co classLoader.getResourceAsStream("resource.txt")zdarzenie, chociaż powinno (ponieważ tak określa Javadoc). [kod źródłowy]

Jednak w wersji 8 nazwa zasobu jest znormalizowana, aby zagwarantować, że używana jest bezwzględna wersja nazwy zasobu. Dlatego w Tomcat 8 dwa opisane powyżej połączenia powinny zawsze zwracać ten sam wynik. [kod źródłowy]

W rezultacie, trzeba być bardzo ostrożnym podczas korzystania ClassLoader.getResourceAsStream()lub Class.getResourceAsStream()w wersjach Tomcat wcześniej niż 8. I trzeba też pamiętać, że class.getResourceAsStream("/resource.txt")faktycznie rozmowy classLoader.getResourceAsStream("resource.txt")(wiodącym /jest usuwany).

LordOfThePigs
źródło
2
Jestem prawie pewien, że getClass().getResourceAsStream("/myfile.txt")zachowuje się inaczej niż getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon
@BrianGordon: Nie zachowują się inaczej. W rzeczywistości javadoc dla Class.getResourceAsStream (String) mówi następującą rzecz: „Ta metoda deleguje się do modułu ładującego klasy tego obiektu.”, A następnie podaje kilka reguł, w jaki sposób konwertuje ścieżkę względną na ścieżkę bezwzględną przed delegowaniem do moduł ładujący.
LordOfThePigs,
@LordOfThePigs Spójrz na faktyczne źródło. Class.getResourceAsStream usuwa wiodący ukośnik do przodu, jeśli podasz ścieżkę bezwzględną.
Brian Gordon,
4
@BrianGordon: Co sprawia, że ​​zachowuje się dokładnie tak samo jak ClassLoader.getResourceAsStream (), ponieważ ten ostatni interpretuje wszystkie ścieżki jako bezwzględne, niezależnie od tego, czy zaczynają się od wiodącego ukośnika, czy nie. Tak długo, jak ścieżka jest absolutna, obie metody zachowują się identycznie. Jeśli twoja ścieżka jest względna, zachowanie jest inne.
LordOfThePigs
Nie mogłem znaleźć getClassLoader()od String, jest to błąd lub potrzebujesz rozszerzenie?
AaA
21

Służy MyClass.class.getClassLoader().getResourceAsStream(path)do ładowania zasobów powiązanych z Twoim kodem. Użyj MyClass.class.getResourceAsStream(path)jako skrótu i ​​do zasobów spakowanych w pakiecie klasy.

Służy Thread.currentThread().getContextClassLoader().getResourceAsStream(path)do uzyskiwania zasobów, które są częścią kodu klienta, a nie ściśle związane z kodem wywołującym. Powinieneś być ostrożny, ponieważ moduł ładujący klasy kontekstu wątku może wskazywać na wszystko.

Tom Hawtin - tackline
źródło
6

Zwykły stary Java na zwykłym starym Java 7 i żadna inna zależność nie pokazuje różnicy ...

Włożyłem file.txtw c:\temp\i umieścić c:\temp\na ścieżce klasy.

Jest tylko jeden przypadek, w którym istnieje różnica między dwoma połączeniami.

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
źródło
bardzo dziękuję, dla mnie działało tylko „J.class.getResourceAsStream („ file.txt ”)”
abbasalim
3

Wszystkie te odpowiedzi tutaj, a także odpowiedzi na to pytanie sugerują, że ładowanie bezwzględnych adresów URL, takich jak „/foo/bar.properties”, traktowane było tak samo przez class.getResourceAsStream(String)i class.getClassLoader().getResourceAsStream(String). Tak NIE jest, przynajmniej nie w mojej konfiguracji / wersji Tomcat (obecnie 7.0.40).

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

Przepraszam, nie mam absolutnie żadnego satysfakcjonującego wyjaśnienia, ale wydaje mi się, że kocur robi brudne sztuczki i swoją czarną magię z modułami ładującymi klasy i powoduje różnicę. Zawsze używałem class.getResourceAsStream(String)w przeszłości i nie miałem żadnych problemów.

PS: Tutaj też to opublikowałem

Tim Büthe
źródło
Może tomcat postanawia nie przestrzegać specyfikacji i nie traktuje wszystkich ścieżek ClassLoader.getResourceAsStream()jako absolutnych? Jest to prawdopodobne, ponieważ, jak wspomniano w niektórych komentarzach powyżej, Class.getResourceAsStreamfaktycznie wywołuje getClassLoader (). GetResourceAsStream`, ale usuwa wszelkie wiodące ukośniki.
LordOfThePigs,
Po sprawdzeniu kodu źródłowego Java SE myślę, że mam odpowiedź: Zarówno ostatecznie, jak Class.getResourceAsStream()i ClassLoader.getResourceAsStream()ostatecznie wywołujemy ClassLoader.findResource()metodę chronioną, której domyślna implementacja jest pusta, ale której javadoc wyraźnie stwierdza „Implementacje modułu ładującego klasy powinny zastąpić tę metodę, aby określić gdzie znaleźć zasoby ". Podejrzewam, że implementacja tej konkretnej metody przez Tomcat może być wadliwa.
LordOfThePigs
Porównałem także implementację WebAppClassLoader.findResource(String name)w Tomcat 7 z implementacją Tomcat 8 i wydaje się, że istnieje zasadnicza różnica. Tomcat 8 wyraźnie znormalizuje nazwę zasobu, dodając wiodące, /jeśli nie zawiera, co powoduje, że wszystkie nazwy są bezwzględne. Tomcat 7 nie. To wyraźnie błąd w Tomcat 7
LordOfThePigs
Dodałem akapit na ten temat w swojej odpowiedzi.
LordOfThePigs
0

Po wypróbowaniu kilku sposobów bezskutecznego załadowania pliku przypomniałem sobie, że mogę go użyć FileInputStream, co działało idealnie.

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

Jest to kolejny sposób na odczytanie pliku do pliku InputStream, który odczytuje plik z aktualnie uruchomionego folderu.

António Almeida
źródło
To nie jest plik, to zasób. Odpowiedź jest nieprawidłowa.
Markiz Lorne
1
@EJP Kończę w odpowiedzi na SO, szukając sposobów na załadowanie pliku, nie znając różnicy między plikiem a zasobem. Nie zamierzam usuwać mojej odpowiedzi, ponieważ może to pomóc innym.
António Almeida,
-3

Działa, wypróbuj to:

InputStream in_s1 =   TopBrandData.class.getResourceAsStream("/assets/TopBrands.xml");
Jaspreet Singh
źródło