Из заметок о выпуске 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
- по этой причине я думаю, что это ценный вопрос.