Подтвердить что ты не робот

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

4b9b3361

Ответ 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/]