Как безопасно получить доступ к URL-адресам всех файлов ресурсов в пути к классам Java 9/10?
Из заметок о выпуске Java 9 мы узнали, что
Загрузчик класса приложения больше не является экземпляром java.net.URLClassLoader (деталь реализации, которая никогда не указывалась в предыдущих выпусках). Код, предполагающий, что ClassLoader :: getSytemClassLoader возвращает объект URLClassLoader, должен быть обновлен.
Это разрушает старый код, который сканирует путь к классам следующим образом:
Java <= 8
URL[] ressources = ((URLClassLoader) classLoader).getURLs();
который
java.lang.ClassCastException:
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to
java.base/java.net.URLClassLoader
Таким образом, для Java 9+ было предложено следующее обходное решение как PR в проекте Apache Ignite Project, который работает по назначению с учетом настроек в --add-opens java.base/jdk.internal.loader=ALL-UNNAMED
JVM: --add-opens java.base/jdk.internal.loader=ALL-UNNAMED
, Однако, как упоминалось в комментариях ниже, этот PR никогда не сливался с их филиалом "Мастер".
/*
* Java 9 + Bridge to obtain URLs from classpath...
*/
private static URL[] getURLs(ClassLoader classLoader) {
URL[] urls = new URL[0];
try {
//see https://github.com/apache/ignite/pull/2970
Class builtinClazzLoader = Class.forName("jdk.internal.loader.BuiltinClassLoader");
if (builtinClazzLoader != null) {
Field ucpField = builtinClazzLoader.getDeclaredField("ucp");
ucpField.setAccessible(true);
Object ucpObject = ucpField.get(classLoader);
Class clazz = Class.forName("jdk.internal.loader.URLClassPath");
if (clazz != null && ucpObject != null) {
Method getURLs = clazz.getMethod("getURLs");
if (getURLs != null) {
urls = (URL[]) getURLs.invoke(ucpObject);
}
}
}
} catch (NoSuchMethodException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
logger.error("Could not obtain classpath URLs in Java 9+ - Exception was:");
logger.error(e.getLocalizedMessage(), e);
}
return urls;
}
Однако это вызывает сильную головную боль из-за использования Reflection здесь. Это своего рода анти-шаблон и строго критикуется плагином forbidden-apis maven:
Запрещенный вызов метода: java.lang.reflect.AccessibleObject # setAccessible (boolean) [Использование рефлексии для работы с флажками доступа не работает с SecurityManagers и, скорее всего, больше не будет работать на классах времени выполнения в Java 9]
Вопрос
Есть ли безопасный способ доступа к списку всех URLs
ресурсов в пути class-/module, к которому можно получить доступ данного загрузчика классов, в OpenJDK 9/10 без использования sun.misc.*
(например, с помощью Unsafe
)?
ОБНОВЛЕНИЕ (связанные с комментариями)
Я знаю, что могу
String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
чтобы получить элементы в пути к классам, а затем проанализировать их по URL
. Однако, насколько мне известно, это свойство возвращает только путь класса, указанный во время запуска приложения. Однако в среде контейнера это будет один из серверов приложений и может оказаться недостаточным, например, с использованием пакетов EAR.
ОБНОВЛЕНИЕ 2
Спасибо всем за ваши комментарии. Я буду тестировать, если System.getProperty("java.class.path")
будет работать для наших целей и обновлять вопрос, если это полностью удовлетворяет наши потребности.
Однако, похоже, что другие проекты (возможно, по другим причинам, например Apache TomEE 8) страдают от той же боли, связанной с URLClassLoader
- по этой причине я думаю, что это ценный вопрос.
Ответы
Ответ 1
Я думаю, что это проблема XY. Доступ к URL-адресам всех ресурсов в пути к классам не поддерживается в Java, и это нехорошо пытаться сделать. Как вы уже видели в этом вопросе, вы будете бороться с каркасом полностью, если попытаетесь это сделать. Будет миллион крайних случаев, которые нарушат ваше решение (пользовательские загрузчики классов, контейнеры EE и т.д.).
Пожалуйста, не могли бы вы объяснить, почему вы хотите это сделать?
Если у вас есть какая-то система плагинов и вы ищете модули, которые взаимодействуют с вашим кодом, который может быть предоставлен во время выполнения, тогда вы должны использовать API ServiceLoader, то есть:
Поставщик услуг, который упакован как файл JAR для пути к классу, идентифицируется путем размещения файла конфигурации поставщика в каталоге ресурсов META-INF/services
. Имя файла конфигурации поставщика - это полное имя двоичного имени службы. Файл конфигурации поставщика содержит список полнофункциональных двоичных имен поставщиков услуг, по одному на строку. Например, предположим, что поставщик услуг com.example.impl.StandardCodecs
упакован в файл JAR для пути к классу. Файл JAR будет содержать файл конфигурации поставщика с именем:
META-INF/services/com.example.CodecFactory
который содержит строку:
com.example.impl.StandardCodecs # Standard codecs
Ответ 2
AFAIK вы можете проанализировать java.class.path
системы java.class.path
для получения URL-адресов:
String classpath = System.getProperty("java.class.path");
String[] entries = classpath.split(File.pathSeparator);
URL[] result = new URL[entries.length];
for(int i = 0; i < entries.length; i++) {
result[i] = Paths.get(entries[i]).toAbsolutePath().toUri().toURL();
}
System.out.println(Arrays.toString(result)); // e.g. [file:/J:/WS/Oxygen-Stable/jdk10/bin/]