Location-Independent Access to Resources
Overview
Methods in the classes Class and ClassLoader provide a location-independent way to locate resources. For example, they enable locating resources for:
These methods do not provide specific support for locating localized resources. Localized resources are supported by the internationalization facilities.
Resources, names, and contexts
The name of a resource is independent of the Java implementation; in particular, the path separator is always a slash (/). However, the Java implementation controls the details of how the contents of the resource are mapped into a file, database, or other object containing the actual resource.
The interpretation of a resource name is relative to a class loader instance. Methods implemented by the ClassLoader class do this interpretation.
System Resources
Non-System Resources
The implementation of getResource on a class loader depends on the details of the ClassLoader class. For example, AppletClassLoader :
All class loaders will search for a resource first as a system resource, in a manner analogous to searcing for class files. This search rule permits overwriting locally any resource. Clients should choose a resource name that will be unique (using the company or package name as a prefix, for instance).
Resource Names
The resource name given to a Class method may have an initial starting «/» that identifies it as an «absolute» name. Resource names that do not start with a «/» are «relative».
Absolute names are stripped of their starting «/» and are passed, without any further modification, to the appropriate ClassLoader method to locate the resource. Relative names are modified according to the convention described previously and then are passed to a ClassLoader method.
Using Methods of java.lang.Class
The Class class implements several methods for loading resources.
The method getResource() returns a URL for the resource. The URL (and its representation) is specific to the implementation and the JVM (that is, the URL obtained in one runtime instance may not work in another). Its protocol is usually specific to the ClassLoader loading the resource. If the resource does not exist or is not visible due to security considerations, the methods return null.
Client code can also request the contents of the resource as an object by applying the java.net.URL.getContent() method on the URL. This is useful when the resource contains the data for an image, for instance. In the case of an image, the result is an awt.image.ImageProducer object, not an Image object.
The getResource and getResourceAsStream methods find a resource with a given name. They return null if they do not find a resource with the specified name. The rules for searching for resources associated with a given class are implemented by the class’s ClassLoader. The Class methods delegate to ClassLoader methods, after applying a naming convention: if the resource name starts with «/», it is used as is. Otherwise, the name of the package is prepended, after converting all periods (.) to slashes (/).
The resolveName method adds a package name prefix if the name is not absolute, and removes any leading «/» if the name is absolute. It is possible, though uncommon, to have classes in diffent packages sharing the same resource.
Using Methods of java.lang.ClassLoader
The ClassLoader class has two sets of methods to access resources. One set returns an InputStream for the resource. The other set returns a URL. The methods that return an InputStream are easier to use and will satisfy many needs, while the methods that return URLs provide access to more complex information, such as an Image and an AudioClip.
The ClassLoader manges resources similarly to the way it manages classes. A ClassLoader controls how to map the name of a resource to its content. ClassLoader also provides methods for accessing system resources, analogous to the system classes. The Class class provides some convenience methods that delegate functionality to the ClassLoader methods.
The methods in ClassLoader use the given String as the name of the resource without applying any absolute/relative transformation (see the methods in Class). The name should not have a leading «/».
System resources are those that are handled by the host implemenation directly. For example, they may be located in the CLASSPATH.
The name of a resource is a «/»-separated sequence of identifiers. The Class class provides convenience methods for accessing resources; the methods implement a convention where the package name is prefixed to the short name of the resource.
The getSystemResourceAsStream method returns an InputStream for the specified system resource or null if it does not find the resource. The resource name may be any system resource.
The getResourceAsStream method returns an InputStream for the specified resource or null if it does not find the resource.
Security
Since getResource() provides access to information, it must have well-defined and well-founded security rules. If security considerations do not allow a resource to be visible in some security context, the getResource() method will fail (return null) as if the resource were not present at all, this addresses existence attacks.
The system ClassLoader provides access to information in the CLASSPATH. A CLASSPATH may contain directories and JAR files. Since a JAR file is created intentionally, it has a different significance than a directory where things may end up in a more casual manner. In particular, we are more strict on getting information out of a directory than out from a JAR file.
If a resource is in a directory:
If the resource is in a JAR file:
Examples
This section provides two examples of client code. The first example uses «absolute resource» names and traditional mechanisms to get a Class object.
Исследование проблем производительности вызова ClassLoader.getResourceAsStream
Если Вам интересно, как вызов метода ClassLoader.getResourceAsStream() в Android приложении может занимать 1432ms и насколько опасны могут быть некоторые библиотеки — прошу под кат.
Описание проблемы
Исследуя проблемы производительности в android приложениях, я заметил данный метод. Проблема проявлялась только при самом первом вызове, все последующие занимали несколько миллисекунд. Интересной особенностью было то, что проблема есть в очень большом количестве приложений, начиная от Amazon’s Kindle с более чем 100,000,000 скачиваний и заканчивая мелкими с парой сотен загрузок.
Другая особенность заключается в том, что в разных приложениях этот метод занимал совершенно разное время. Вот, например, время для популярнейшего приложения для селфи: B612 — Selfie from the heart
Как видим, тут метод занимаем 771ms, что тоже не мало, но намного меньше чем 1432ms.
Еще немного очень популярных приложений, чтобы подчеркнуть, насколько велика проблема.
(Для профилирования приложений, использовался сервис https://nimbledroid.com)
| Приложение | Время выполнения getResourceAsStream() |
|---|---|
| Yahoo Fantasy Sports | 2166ms |
| Timehop | 1538ms |
| Audiobooks from Audible | 1527ms |
| Nike+ Running | 1432ms |
| Booking.com Hotel Reservations | 497ms |
И еще много много других.
Давайте внимательно посмотрим на Call Stack некоторых приложений.
LINE: Free Calls & Messages:
Как мы видим, вызов getResourceAsStream выполняется НЕ в главном потоке. Это значит, что разработчики Line знают о том, насколько он медленный.
Yahoo Fantasy Sports:
Вызов происходит не в коде приложения, а внутри библиотеки JodaTime
Audiobooks from Audible
Вызов происходит в библиотеке логирование Logback
После анализа приложений с этой проблемой, становится понятно, что этот вызов в основном используется в различных библиотеках и SDK, которые распространяются в виде Jar файлов. Это логично, так как таким образом мы можем получить доступ к ресурсам, более того, этот код будет работать и на Android, и в мире большой Java. Многие приходят в Android разработку имея Java опыт и, естественно, начинают использовать знакомые библиотеки, не зная о том, насколько замедляют свои приложения. Думаю теперь понятно, почему затронуто такое большое количество приложений.
Исследование проблемы
Я буду использовать ветку android-6.0.1_r11
Давайте откроем файл libcore/libart/src/main/java/java/lang/ClassLoader.java и посмотрим на код getResourceAsStream:
Все выглядит довольно просто, сначала находим путь к ресурсу, и если он не null, то открываем его с помощью метода openStream(), который есть в java.net.URL
Давайте посмотрим на реализацию getResource():
Все еще ничего интересного, findResource():
Хочется в этом убедиться, поэтому я собрал AOSP, предварительно модифицировав getResourceAsStream() подобным образом:
Я получил то, что и ожидалось — dalvik.system.PathClassLoader, но если мы проверим исходники PathClassLoader, мы не найдем реализации findResource(). На самом деле findResource() реализован в родительском классе — BaseDexClassLoader.
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java:
Давайте найдем pathList (я специально не удаляю комментарии разработчиков, чтобы было проще понять, что есть что):
Кажется мы нашли место, где реально выполняется поиск ресурса.
Element — это просто статический внутренний класс в DexPathList. И внутри него есть намного более интересный код:
Давайте на этом немного остановимся. Как мы знаем, APK — это просто zip файл. Как мы видим:
мы пытаемся найти ZipEntry по имени, и если находим, то возвращаем java.net.URL. Это может быть довольно медленная операция, но если мы проверим реализацию getEntry, мы увидим, что это просто итерация по LinkedHashMap:
Это не супер быстрая операция, но она не может занимать очень много времени.
Мы упустили одну вещь: прежде чем работать с Zip файлами, мы должны открыть их. Если мы снова посмотрим на реализацию метода DexPathList.Element.findResource(), мы увидим вызов maybeInit();.
Давайте проверим его:
Вот оно! Эта строка:
открывает zip файл на чтение.
Это очень медленная операция, тут мы инициализируем entries LinkedHashMap. Очевидно, что чем больше zip файл, тем больше времени потребуется на его открытие. Из-за флага initialized мы открываем zip файл только один раз, это обьясняет почему последующие вызовы происходят быстро.
Надеюсь это было интересно! Потому что это — только начало.
На самом деле, пока мы разобрались только с вызовом:
Но если мы изменим код getResourceAsStream таким образом:
соберем AOSP и протестируем пару приложений, то мы увидим, что url.openStream() занимает значительно больше времени, чем getResource().
url.openStream()
В этой части я опущу некоторые не очень интересные моменты. Если мы будем двигаться по цепочке вызовов от url.openStream(), то мы попадем в /libcore/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java:
Давайте проверим метод connect():
Ничего интересного, we need to go deeper 🙂
Это интересный метод и тут мы остановимся. getUseCaches() через цепочку вызовов приведет нас к
Это значение не переопределяется, поэтому мы увидим использование cache:
В методе findJarFile() мы видим вторую проблему производительности! Zip файл открывается снова! Естественно это не будет работать быстро, особенно для приложений размером 40mb 🙂
Есть еще один интересный момент, давайте проверим метод openJarFile():
Как вы видите, мы создаем НЕ ZipFile, а JarFile. JarFile наследник ZipFile, давайте проверим, что он добавляет:
Ага, вот и разница! Как мы знаем, APK файл должен быть подписан, и класс JarFile это проверит.
Но, что нужно сказать, так это то, что это очень очень медленный процесс.
Заключение
Когда ClassLoader.getResourceAsStream() вызывается первый раз, APK файл открывается как zip файл, чтобы найти ресурс. После этого он открывается второй раз, но уже с верификацией, чтобы открыть InputStream. Это также обьясняет, почему такая большая разница в скорости работы вызова для разных приложений, все зависит от размера APK файла и насколько много файлов внутри!
One more thing
Q: Есть ли какая нибудь разница между Dalvik и ART runtime и работой вызова getResourceAsStream()
A: На самом деле нет, я проверил несколько веток AOSP android-6.0.1_r11 с ART и android-4.4.4_r2 с Dalvik. Проблема есть в обеих!
Разница между ними немного в другом, но об этом много написано 🙂
Q: Почему нет такой проблемы при вызове ClassLoader.findClass()
A: Если мы перейдем в уже знакомый нам класс DexPathList, то мы увидим:
Проследовав по цепочке вызовов мы прийдем к методу:
И дальше то, как это будет работать, будет зависеть от runtime (ART или Dalvik), но как мы видим, никакой работы с ZipFile.
Q: Почему у вызовов Resources.get… (resId) нет этой проблемы
A: По той же причине, что и у ClassLoader.findClass().
Все эти вызовы приведут нас к /frameworks/base/core/java/android/content/res/AssetManager.java
Спасибо за внимание! Happy coding!
Русские Блоги
Использование getResourceAsStream в Java
Прежде всего, в Java есть следующие типы getResourceAsStream:
1. Class.getResourceAsStream(String path) : Если путь не начинается с «/», по умолчанию выбираются ресурсы из пакета, в котором расположен этот класс, и начинаются с «/» для получения ресурсов из корня ClassPath. Он просто создает абсолютный путь через путь и, наконец, получает ресурс с помощью ClassLoader к
2. Class.getClassLoader.getResourceAsStream(String path) :По умолчанию он получен из корня ClassPath. Путь не может начинаться с ‘/’, и ресурс наконец получен ClassLoader. к
3. ServletContext. getResourceAsStream(String path):По умолчанию ресурсы выбираются из корневого каталога WebAPP. Не имеет значения, начинается ли путь в Tomcat с «/». Конечно, это связано с конкретной реализацией контейнера. к
4. Встроенный объект приложения под Jsp является реализацией вышеуказанного ServletContext. к
Во-вторых, использование getResourceAsStream примерно следующее:
Затем должен быть следующий код:
второй:Например, в подкаталоге каталога me.class есть класс me.class в com.x.y, а файл ресурсов myfile.xml в каталоге com.x.y.file.
Затем должен быть следующий код:
Затем должен быть следующий код:
В итоге может быть только два способа написания
первый:Предшествует «/»
«/» представляет корневой каталог проекта, например, именем проекта является myproject, а «/» представляет myproject
второй:Там нет «/» впереди
представляет каталог текущего класса
Русские Блоги
Использование метода getResourceAsStream ClassLoader и проблемы с путями в Java и веб-проектах
ClassLoader, загрузчик классов с небольшим именем, звучит как большая капля, объяснение онлайн-бога эзотерическое, только начинается, его трудно понять. Насколько я понимаю, для загрузки некоторых файлов, наиболее часто используемый метод называется getResourceAsStream (), который используется для загрузки некоторых файлов конфигурации. Хорошо, введите вопрос:
Код выглядит следующим образом:
Путь должен быть скорректирован следующим образом:
Вывод: сравнение двух рисунков показывает, что путь к загрузчику классов относительно каталога bin, который хранится после компиляции java-файла:
Начальный каталог bin выглядит следующим образом:
Позже это было так:
Более того, загрузчик классов может загружать только файлы в каталоге классов, а другие каталоги не могут быть загружены.
2 То же самое относится и к динамическим веб-проектам, единственное отличие состоит в том, что относительный путь в это время отличается, а относительным является каталог классов в WEB-INF после развертывания.
Та же проблема в том, что этот загрузчик классов не может загружать файлы вне каталога классов, например, помещая файл конфигурации в WebRoot, так что путь вообще не может быть записан, не говоря уже о загрузке файла.
Чтобы решить эту проблему, вы можете использовать объект ServletContext, который может загружать файлы в каталог классов или файлы в WebRoot.
В настоящее время косая черта в начале пути представляет текущее приложение. Например, если мой проект Web8, то это Web8 /. Кто-то может спросить? Почему бы не Web8 / WebContent /, потому что нет каталога WebContent после развертывания проекта. Как показано ниже:
Различные способы загрузки файла в качестве InputStream
в чем разница между:
когда каждый из них более подходит для использования, чем другие?
файл, который я хочу прочитать это в classpath, как мой класс, который читает файл. Мой класс и файл находятся в одной банке и упакованы в файл EAR и развернуты в WebSphere 6.1.
6 ответов
каждый раз, когда я упоминаю местоположение в этом сообщении, это может быть местоположение в вашей файловой системе или внутри соответствующий файл jar, в зависимости от класса и/или загрузчика классов, из которого вы загружаете ресурс.
читать в этой статье для получения более подробной информации об этой конкретной проблеме.
предупреждение для пользователей Tomcat 7 и ниже
один из ответов на этот в вопросе говорится, что мое объяснение кажется неправильным для Tomcat 7. Я пытался понять, почему это так.
Итак, я просмотрел исходный код Tomcat’s 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») (ведущий / лишен).
использовать MyClass.class.getClassLoader().getResourceAsStream(path) для загрузки ресурса, связанного с вашим кодом. Использовать MyClass.class.getResourceAsStream(path) как ярлык и для ресурсов, упакованных в пакет вашего класса.
использовать Thread.currentThread().getContextClassLoader().getResourceAsStream(path) чтобы получить ресурсы, которые являются частью клиентского кода, не плотно ограничивает вызывающий код. Вы должны быть осторожны с этим, так как загрузчик класса контекста потока может указывать на что угодно.
обычная старая Java на простой старой Java 7 и никакие другие зависимости не демонстрируют разницу.
Я поставил file.txt на c:\temp\ и я ставлю c:\temp\ на пути к классу.
существует только один случай, когда существует разница между двумя вызовами.
Извините, у меня нет абсолютно никакого удовлетворительного объяснения, но я думаю, что tomcat делает грязные трюки и свою черную магию с загрузчиками классов и вызывает разницу. Я всегда использовал class.getResourceAsStream(String) в прошлом, и не было никаких проблем.
PS: Я также разместил это здесь



