Загрузка классов и ресурсов в Java 9
Я читал эту статью на InfoQ со ссылкой на Рейнхольда:
Разработчики могут по-прежнему использовать путь классов Java в Java 9 для среды выполнения Java для поиска классов и файлов ресурсов. Просто с модулями Java 9 разработчикам больше не нужен путь к классам.
Итак, теперь мой вопрос: как правильно использовать Java 9 для выполнения задач, перечисленных выше? Как вы динамически загружаете, например, изображение (если не брать в расчет относительные пути)?
Еще более интересно, как можно было бы проверить, доступен ли класс, и принять решение динамически (например, проверить, доступен ли Джексон и, если да, использовать его для сериализации JSON, а если не использовать что-то еще)?
В статье также упоминается Spring Boot, уже поддерживающий Java 9, и Spring Boot определенно выполняет большую динамическую загрузку. Так что, может быть, кто-то знает источник кода из Spring, на который я могу посмотреть?
3 ответа
Во-первых, чтобы исправить ситуацию, я не сказал и не написал приведенный выше текст. Я бы никогда так не сказал. Это просто неаккуратная отчетность со стороны соответствующих публикаций.
Самая важная вещь для понимания загрузки классов и поиска ресурсов в Java 9 состоит в том, что на фундаментальном уровне они не изменились. Вы можете искать классы и ресурсы так же, как всегда, вызывая Class::forName
и различные getResource*
методы в Class
а также ClassLoader
классы, независимо от того, загружен ли ваш код из пути класса или пути модуля. Есть все еще три встроенных загрузчика классов, как это было в JDK 1.2, и они имеют те же отношения делегирования. Поэтому много существующего кода просто работает, из коробки.
Есть некоторые нюансы, как отмечалось в JEP
261. Конкретный тип встроенных загрузчиков классов изменился, и некоторые классы, ранее загруженные загрузчиком классов начальной загрузки, теперь загружаются загрузчиком классов платформы для повышения безопасности. Существующий код, который предполагает, что встроенный загрузчик классов является URLClassLoader
или то, что класс загружается загрузчиком класса начальной загрузки, поэтому может потребовать незначительных корректировок.
И последнее важное отличие состоит в том, что ресурсы, не относящиеся к файлам классов, в модуле инкапсулированы по умолчанию и, следовательно, не могут быть расположены снаружи модуля, если их эффективный пакет не являетсяopen
, Для загрузки ресурсов из вашего собственного модуля лучше всего использовать методы поиска ресурсов в Class
или же Module
, который может найти любой ресурс в вашем модуле, а не в ClassLoader
, который может найти только ресурсы вне класса в open
пакеты модуля.
[Изменить: этот ответ был написан до авторитетного ответа Марка. Я пересмотрел мой, чтобы предоставить простой пример, доступный на GitHub.]
Согласно этому видео, загрузка классов в Java 9 не изменилась.
В качестве примера, скажем, у нас есть:
example.jar
который содержит изображение в упаковкеnet.codetojoy.example.resources
- поднять банку,
net.codetojoy.example.Composer
является общедоступным (и экспортируется, где это применимо) - просто
App
класс, который используетexample.jar
как библиотека и пытается загрузить из нее изображение
Соответствующий код в App
:
static InputStream getResourceAsStream(String resource)
throws Exception {
// Load net/codetojoy/example/resource/image.jpg
// Assume net.codetojoy.example.Composer is public/exported
// resource is 'resource/image.jpg'
InputStream result = Composer.class.getResourceAsStream(resource);
return result;
}
Вот несколько случаев для example.jar
в JDK 9:
Старомодная немодульная банка
Если example.jar
это не модуль, код просто работает. Класс загрузки неизменен.
Модульная банка с открытой упаковкой
В этом случае это module-info.java
файл:
module net.codetojoy.example {
// export the Composer class
exports net.codetojoy.example;
// image is available
opens net.codetojoy.example.resources;
}
В этом случае изображение может быть загружено клиентом, поскольку пакет открыт.
Модульная банка без открытой упаковки
В этом случае, module-info.java
является:
module net.codetojoy.example {
// export the Composer class
exports net.codetojoy.example;
// package not opened: image not available
// opens net.codetojoy.example.resources;
}
В этом случае изображение не может быть загружено из-за сильной инкапсуляции: модуль защищает изображение, не открывая пакет.
Полный исходный код здесь на GitHub.
В дополнение к существующим ответам я хотел бы привести пример правил инкапсуляции для ресурсов, не относящихся к файлам классов, для разных имен каталогов ресурсов.
В спецификации говорится, что ресурс инкапсулируется, если имя пакета является производным от его имени.
Поэтому, если getResourceAsStreamимя каталога ресурса НЕ является допустимым идентификатором Java , оно НЕ инкапсулируется. Это означает, что если у модуля есть ресурс, расположенный, например, в каталоге с именем
dir-1
(содержащий недопустимый символ
-
в его названии) он всегда будет доступен извне модуля.
Вот пример двух модулей Java (исходный код в GitHub). Модуль 1 состоит из следующих файлов ресурсов:
├── dir-3
│ └── resource3.txt
├── dir1
│ └── resource1.txt
├── dir2
│ └── resource2.txt
└── root.txt
а также :
module module_one {
opens dir1;
}
Для модуля 2 требуется модуль 1 в
module-info.java
:
module module_two {
requires module_one;
}
и имеет образец основного класса для загрузки различных файлов ресурсов:
package module2;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
loadResource("root.txt", "From module's root directory");
loadResource("dir1/resource1.txt", "From opened package `dir1`");
loadResource("dir2/resource2.txt", "From internal package `dir2`");
loadResource("dir-3/resource3.txt", "From directory `dir-3` with non-Java name");
}
public static void loadResource(String name, String comment) throws IOException {
// module2 application class loader
final var classLoader = Main.class.getClassLoader();
try (var in = classLoader.getResourceAsStream(name)) {
System.out.println();
System.out.println("// " + comment);
System.out.println(name + ": " + (in != null));
}
}
}
Запуск приведенного выше класса дает следующий результат:
// From module's root directory
root.txt: true
// From opened package `dir1`
dir1/resource1.txt: true
// From internal package `dir2`
dir2/resource2.txt: false
// From directory `dir-3` with non-Java name
dir-3/resource3.txt: true
Как вы можете видеть файл ресурсов из корневого каталога и из
dir-3
каталог не инкапсулирован, поэтому модуль 2 может их загрузить.
Пакет инкапсулирован, но безоговорочно открыт. Модуль 2 тоже может его загрузить.
Пакет инкапсулирован и не открывается. Модуль 2 не может его загрузить.
Обратите внимание, что Модуль 2 не может содержать собственные ресурсы в рамках и
dir2
каталоги, потому что они уже инкапсулированы в Модуле 1 . Если вы попробуете добавить
dir1
вы получите следующую ошибку:
Error occurred during initialization of boot layer
java.lang.LayerInstantiationException: Package dir1 in both module module_one and module module_two