Различные способы загрузки файла как InputStream


216

Какая разница между:

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

и

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

и

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

Когда каждый из них более подходит для использования, чем другие?

Файл, который я хочу прочитать, находится в classpath как мой класс, который читает файл. Мой класс и файл находятся в одном банке и упакованы в файл EAR и развернуты в WebSphere 6.1.

Ответы:


289

Существуют тонкие различия в том, как fileNameинтерпретируется передаваемый вами текст. В принципе, у вас есть 2 разные методы: ClassLoader.getResourceAsStream()иClass.getResourceAsStream() . Эти два метода будут определять местоположение ресурса по-разному.

В Class.getResourceAsStream(path), путь интерпретируется как локальный путь к пакету класса, из которого вы вызываете его. Например , призванию, String.getResourceAsStream("myfile.txt")будет искать файл в вашем пути к классам по следующему адресу: "java/lang/myfile.txt". Если ваш путь начинается с a /, то он будет считаться абсолютным путем и начнет поиск с корня пути к классам. Таким образом, вызов String.getResourceAsStream("/myfile.txt")будет смотреть на следующее место в вашем пути к классу ./myfile.txt.

ClassLoader.getResourceAsStream(path)будет считать все пути абсолютными. Так что вызов String.getClassLoader().getResourceAsStream("myfile.txt")и String.getClassLoader().getResourceAsStream("/myfile.txt")оба будут искать файл в вашем classpath в следующем месте:./myfile.txt .

Каждый раз, когда я упоминаю местоположение в этом посте, это может быть местоположение в самой вашей файловой системе или внутри соответствующего файла JAR, в зависимости от класса и / или ClassLoader, из которого вы загружаете ресурс.

В вашем случае вы загружаете класс с сервера приложений, поэтому вы должны использовать Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)вместо this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()тоже будет работать.

Прочтите эту статью для получения более подробной информации об этой конкретной проблеме.


Предупреждение для пользователей Tomcat 7 и ниже

Один из ответов на этот вопрос гласит, что мое объяснение кажется неправильным для Tomcat 7. Я попытался осмотреться, чтобы понять, почему это так.

Поэтому я посмотрел исходный код Tomcat WebAppClassLoaderдля нескольких версий Tomcat. Реализация findResource(String name)(которая в конечном итоге отвечает за создание URL-адреса запрашиваемого ресурса) практически идентична в Tomcat 6 и Tomcat 7, но отличается в Tomcat 8.

В версиях 6 и 7 реализация не пытается нормализовать имя ресурса. Это означает, что в этих версиях, classLoader.getResourceAsStream("/resource.txt")возможно, не будет получен тот же результат, что и у classLoader.getResourceAsStream("resource.txt")события, хотя это и должно быть (поскольку это то, что указывает Javadoc). [исходный код]

В версии 8 имя ресурса нормализовано, чтобы гарантировать, что используется абсолютная версия имени ресурса. Следовательно, в Tomcat 8 два вышеописанных вызова должны всегда возвращать один и тот же результат.[исходный код]

В результате вы должны быть особенно осторожны при использовании ClassLoader.getResourceAsStream()или Class.getResourceAsStream()в версиях Tomcat более ранних, чем 8. И вы также должны помнить, что на class.getResourceAsStream("/resource.txt")самом деле вызовы classLoader.getResourceAsStream("resource.txt")(ведущий /удаляется).


2
Я почти уверен, что getClass().getResourceAsStream("/myfile.txt")ведет себя не так getClassLoader().getResourceAsStream("/myfile.txt").
Брайан Гордон

@BrianGordon: они не ведут себя иначе. На самом деле, javadoc для Class.getResourceAsStream (String) говорит следующее: «Этот метод делегирует загрузчику классов этого объекта.», А затем дает набор правил о том, как он преобразует относительный путь в абсолютный путь, прежде чем делегировать Загрузчик классов.
LordOfThePigs

@LordOfThePigs Посмотрите на фактический источник. Class.getResourceAsStream удаляет начальную косую черту, если вы указываете абсолютный путь.
Брайан Гордон

4
@BrianGordon: что заставляет его вести себя точно так же, как ClassLoader.getResourceAsStream (), так как последний интерпретирует все пути как абсолютные, независимо от того, начинаются они с начального слеша или нет. Так что, пока ваш путь абсолютен, оба метода ведут себя одинаково. Если ваш путь относительно, то поведение другое.
LordOfThePigs

Я не мог найти getClassLoader()из String, это ошибка или необходимость расширения?
AaA

21

Используйте MyClass.class.getClassLoader().getResourceAsStream(path)для загрузки ресурса, связанного с вашим кодом. Используйте MyClass.class.getResourceAsStream(path)в качестве ярлыка и для ресурсов, упакованных в пакет вашего класса.

Используется Thread.currentThread().getContextClassLoader().getResourceAsStream(path)для получения ресурсов, которые являются частью клиентского кода, а не тесно связаны с вызывающим кодом. Вы должны быть осторожны с этим, поскольку загрузчик класса контекста потока может указывать на что угодно.


6

Обычная старая Java на простой старой Java 7 и никаких других зависимостей демонстрирует разницу ...

Я положил file.txtв c:\temp\и я поставил c:\temp\на пути к классам.

Существует только один случай, когда есть разница между двумя вызовами.

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); 
  }
}

большое спасибо, у меня работал только 'J.class.getResourceAsStream ("file.txt")'
abbasalim

3

Все эти ответы здесь, а также ответы в этом вопросе предполагают, что загрузка абсолютных URL-адресов, таких как "/foo/bar.properties", обрабатывается одинаково с помощью class.getResourceAsStream(String)и class.getClassLoader().getResourceAsStream(String). Это НЕ так, по крайней мере, в моей конфигурации / версии Tomcat (в настоящее время 7.0.40).

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

Извините, у меня нет абсолютно никакого удовлетворительного объяснения, но я предполагаю, что tomcat делает грязные трюки и свою черную магию с загрузчиками классов и вызывает разницу. Я всегда использовал class.getResourceAsStream(String)в прошлом и не было никаких проблем.

PS: я также разместил это здесь


Может быть, tomcat решает не соблюдать спецификацию и не рассматривает все пути, переданные ClassLoader.getResourceAsStream()как абсолютные? Это правдоподобно, потому что, как упоминалось в некоторых комментариях выше, на Class.getResourceAsStreamсамом деле вызывает getClassLoader (). GetResourceAsStream`, но удаляет любой начальный слеш.
LordOfThePigs

После проверки исходного кода Java SE, я думаю, что у меня есть ответ: Class.getResourceAsStream()и то, и другое, в ClassLoader.getResourceAsStream()конечном счете, вызов, ClassLoader.findResource()который является защищенным методом, реализация по умолчанию которого пуста, но чей javadoc явно заявляет «Реализации загрузчика классов должны переопределить этот метод, чтобы указать, где найти ресурсы ". Я подозреваю, что реализация Tomcat этого конкретного метода может быть ошибочной.
LordOfThePigs

Я также сравнил внедрение WebAppClassLoader.findResource(String name)в Tomcat 7 с тем, что в Tomcat 8 , и кажется , что есть принципиальное различие. Tomcat 8 явно нормализует имя ресурса, добавляя ведущий, /если он не содержит, что делает все имена абсолютными. Tomcat 7 нет. Это явно ошибка в Tomcat 7
LordOfThePigs

Я добавил параграф об этом в своем ответе.
LordOfThePigs

0

Попробовав несколько способов загрузить файл безуспешно, я вспомнил, что смог использовать FileInputStream, что сработало отлично.

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

Это еще один способ прочитать файл в файл InputStream, он читает файл из текущей запущенной папки.


Это не файл, это ресурс. Ответ не верный.
Маркиз Лорн

1
@EJP Я заканчиваю в этом ответе SO, ища способы загрузить файл, не зная разницы между файлом и ресурсом. Я не собираюсь удалять свой ответ, потому что он может помочь другим.
Антонио Алмейда,

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.