Как заставить рефлексию работать в JDK 16 и новее?

У меня есть следующий устаревший код, который я перенес на Java 16, но он не работает:

      try {
    Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(URLClassLoader.getSystemClassLoader(), file.toURI().toURL());
} catch (Exception e) {
    e.printStackTrace();
}

... Есть способ заставить его работать?

5 ответов

Решение

Наконец-то я нашел следующие способы решения этой проблемы:

  1. добавляя --add-opens=java.base/java.net=ALL-UNNAMED параметр исполняемого файла JVM (в случае безымянного модуля)
  2. добавляя --add-opens=java.base/java.net=MY-MODULE-NAME параметр исполняемого файла JVM (в случае именованного модуля)
  3. с ядром Burningwave :
    • вызывая метод org.burningwave.core.assembler.StaticComponentContainer.Modules.exportAllToAll()
    • или вызвав метод addClassPath из ClassLoaders составная часть:
      org.burningwave.core.assembler.StaticComponentContainer.ClassLoaders.addClassPath(
    new
    URLClassLoader(
        new URL[] {}
    ), 
    new File("C:/external-folder/my.jar").toURI().toURL()
)
  • или вызвав метод invoke из Methods составная часть:
      org.burningwave.core.assembler.StaticComponentContainer.Methods.invoke(
    new URLClassLoader(new URL[] {}),
    "addURL",
    new File("C:/external-folder/my.jar").toURI().toURL()
);

Проблема не в том, что отражение «не работает»; дело в том, что отражение, наконец, обеспечивает большую часть модели доступности, которую всегда применяли компилятор и среда выполнения. URLClassLoader::addUrlпредназначен только для подклассов; он не предназначен для доступа извне реализации, что вы делаете. Со временем, начиная с Java 9 и продолжая в более поздних версиях (включая 17), ограничения доступа все чаще распознаются путем отражения с предупреждениями, чтобы дать сломанному коду возможность перейти на что-то поддерживаемое.

Рассматриваемый код действительно работал случайно; это зависело от возможности взломать неподдерживаемый интерфейс. С использованием setAccessibleдолжно быть подсказкой. Конечно, вы можете попасть в запертые дома, разбив окна, но если вам нужно разбить окно (а это не ваш дом), вы должны знать, в чем проблема.

Посмотрите на него как на наполовину полный; этот случайно сработавший код работал долгое время. Но счет пришел к оплате; время исправить ваш код.

Код очень странный. На первый взгляд, я подумал, что он использует отражение для доступа к внутренним компонентам, чтобы добавить myJarв существующий загрузчик классов , но вместо этого он использует его для добавления в новый загрузчик классов . Нет причин для этого. Вы можете просто использоватьURLClassLoaderконструктор ) для этого.

Это должно выглядеть примерно так (не проверено, потому что я не использую IDE):

      URL jar = new File("C:/external-folder/my.jar").toURI().toURL();
URL[] urls = { jar };
new URLClassLoader(urls);

Я написал библиотеку Narcissus, которая реализует небольшой API отражения, который полностью обходит все проверки видимости / доступа, средства защиты диспетчера безопасности и строгие меры инкапсуляции в JDK 16+ (на самом деле JDK 7+).

Я слежу за обсуждением с начала этого поста (включая пост о мета stackoverflow) и надеюсь, что меня не убьют :-)

Однако есть два решения для решения этой проблемы без внесения серьезных изменений в код:

  1. разрешить открытие пакета для всех безымянных модулей через аргумент JVM -add-opens=java.base/java.net=ALL-UNNAMED
  2. экспортировать java.net пакет для всех безымянных модулей во время выполнения через burnwave: Modules.exportPackageToAllUnnamed ("java.base", "java.net")

Я знаю, что некоторым людям не нравится второе решение, но на данный момент это единственные решения, позволяющие оставить код без изменений.

Другие вопросы по тегам