В чем разница между Class.getResource() и ClassLoader.getResource()?

Интересно, какая разница между Class.getResource() а также ClassLoader.getResource()?

редактировать: я особенно хочу знать, есть ли какое-либо кэширование на уровне файлов / каталогов. Как в "кэшируются ли списки каталогов в версии класса?"

AFAIK следующие должны по существу делать то же самое, но это не так:

getClass().getResource() 
getClass().getClassLoader().getResource()

Я обнаружил это, когда возился с кодом генерации отчетов, который создает новый файл в WEB-INF/classes/ из существующего файла в этом каталоге. При использовании метода из класса я мог найти файлы, которые были там при развертывании, используя getClass().getResource(), но при попытке извлечь вновь созданный файл, я получил нулевой объект. Просмотр каталога ясно показывает, что новый файл есть. К именам файлов добавляется косая черта, как в "/myFile.txt".

ClassLoader версия getResource() с другой стороны, нашел сгенерированный файл. Из этого опыта кажется, что происходит какое-то кэширование списка каталогов. Я прав, и если да, то где это задокументировано?

Из документов API на Class.getResource()

Находит ресурс с заданным именем. Правила поиска ресурсов, связанных с данным классом, реализуются загрузчиком класса, определяющего класс. Этот метод делегирует загрузчику классов этого объекта. Если этот объект был загружен загрузчиком класса начальной загрузки, метод делегируется ClassLoader.getSystemResource(java.lang.String).

Для меня это звучит так: "Class.getResource действительно вызывает свой собственный загрузчик классов getResource()". Что было бы так же, как делать getClass().getClassLoader().getResource(), Но это явно не так. Может ли кто-нибудь дать мне некоторое представление об этом?

9 ответов

Решение

Чтобы ответить на вопрос, происходит ли кеширование.

Я исследовал этот вопрос далее, запустив отдельное Java-приложение, которое непрерывно загружало файл с диска с помощью метода getResourceAsStream ClassLoader. Я смог отредактировать файл, и изменения были немедленно отражены, т. Е. Файл был перезагружен с диска без кэширования.

Однако: я работаю над проектом с несколькими модулями maven и веб-проектами, которые зависят друг от друга. Я использую IntelliJ в качестве своей IDE для компиляции и запуска веб-проектов.

Я заметил, что вышеупомянутое, похоже, больше не соответствует действительности, потому что файл, который я загружал, теперь запекается в банку и развертывается в зависимом веб-проекте. Я заметил это только после попытки изменить файл в моей целевой папке, но безрезультатно. Это создавало впечатление, что кеширование происходит.

Class.getResource может принимать "относительное" имя ресурса, которое обрабатывается относительно пакета класса. В качестве альтернативы вы можете указать "абсолютное" имя ресурса, используя начальную косую черту. Пути к ресурсам загрузчика классов всегда считаются абсолютными.

Таким образом, следующие в основном эквивалентны:

foo.bar.Baz.class.getResource("xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("foo/bar/xyz.txt");

И вот они (но они отличаются от вышеупомянутого):

foo.bar.Baz.class.getResource("/data/xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("data/xyz.txt");

Первый вызов ищет относительно .class файл, в то время как последний ищет относительно корня пути к классу.

Для устранения подобных проблем я печатаю URL:

System.out.println( getClass().getResource(getClass().getSimpleName() + ".class") );

Пришлось посмотреть это в спецификации:

Класс getResource() - документация утверждает разницу:

Этот метод делегирует вызов своему загрузчику классов после внесения этих изменений в имя ресурса: если имя ресурса начинается с "/", оно не изменяется; в противном случае имя пакета добавляется к имени ресурса после преобразования "." в "/". Если этот объект был загружен загрузчиком начальной загрузки, вызов делегируется ClassLoader.getSystemResource.

Все эти ответы здесь, а также ответы в этом вопросе предполагают, что загрузка абсолютных 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: я также разместил это здесь

Начиная с Java 9, при запуске по пути к модулю есть ловушка. Из-за этого я бы никогда не использовал в новом коде.

Если ваш код находится в именованном модуле и вы его используете, ваш код может не получить ресурс, даже если он находится в том же модуле. Это очень удивительное поведение.

Я испытал это на себе и был очень удивлен этой разнице между Class#getResource а также ClassLoader#getResource. Однако это полностью определенное поведение в соответствии с javadoc:

Кроме того, за исключением особого случая, когда имя ресурса заканчивается на «.class», этот метод будет находить ресурсы только в пакетах с именованными модулями, когда пакет открывается безоговорочно ( даже если вызывающий этот метод находится в том же модуль в качестве ресурса).

Javadoc (выделено мной)

Class.getResources будет получать ресурс загрузчиком классов, который загружает объект. В то время как ClassLoader.getResource получит ресурс, используя указанный загрузчик классов.

Я пытался читать из input1.txt, который был внутри одного из моих пакетов, вместе с классом, который пытался его прочитать.

Следующие работы:

String fileName = FileTransferClient.class.getResource("input1.txt").getPath();

System.out.println(fileName);

BufferedReader bufferedTextIn = new BufferedReader(new FileReader(fileName));

Самой важной частью было позвонить getPath() если вы хотите правильный путь в формате String. НЕ ИСПОЛЬЗОВАТЬtoString() потому что это добавит некоторый дополнительный форматирующий текст, который ПОЛНОСТЬЮ НЕПРАВИЛЬНО ИСПОЛЬЗУЕТ fileName (вы можете попробовать это и посмотреть распечатку).

Потратил 2 часа на отладку этого...:(

Еще один более эффективный способ - просто использовать @Value.

      @Value("classpath:sss.json")
private Resource resource;

и после этого вы можете просто получить файл таким образом

      File file = resource.getFile();

Другие вопросы по тегам