Нестандартный загрузчик классов с getResources для имен ENDING в слэше
Я отчаянно нуждаюсь в помощи, но не смог найти что-либо в Интернете по этому конкретному вопросу (многие связанные, которые оставляют мою конкретную проблему без ответа).
В частности, мне нужно иметь возможность загружать код (jars) из центрального и внешнего хранилища кода. Это делается с помощью загрузочного кода, который должен добавить это к пути к классу загрузчика классов, который будет использоваться впоследствии. Это когда мы входим в предмет, который обсуждался так много раз. Я не люблю хаки, поэтому я попробовал следующее:
Попытка #1: Создайте экземпляр URLClassLoader, настроенный для этой цели, а затем вызовите "остаток" кода через него.
Ошибка: здесь 1,5 проблемы (одна может быть причиной другой). Во-первых, URLClassLoader обычно предпочитает загружать вещи из своего родителя. Некоторый код должен существовать в обеих, возможно, в разных версиях. Если используется код из родительского объекта, он продолжает использовать загрузчик "внешнего" класса для остальной загрузки, а это не то, что нам нужно, даже когда начальная загрузка в порядке. Во-вторых, некоторые сторонние библиотеки, по- видимому, обращаются к загрузчику системных классов напрямую, либо по проекту, либо случайно (могут получить его от одного из загруженных им классов).
Попытка № 2: Создайте мой подкласс URLClassLoader, который предпочитает self родительскому элементу. Переопределите loadClass, getResource, getResources, getPackage, getPackages... позже и другие методы, чтобы убедиться в этом.
Отказ: не помог (достаточно). Этот сторонний код все еще не может загрузить некоторые ресурсы.
Попытка № 3 Создайте другой пользовательский подкласс URLClassLoader и установите его в качестве загрузчика системного класса, используя -Djava.system.class.loader=...
Неудача: это сработало лучше - пошло дальше, но все равно не удалось получить ресурсы. На этот раз это были разные ресурсы. Я добавил логирование для всех переопределенных методов, чтобы регистрировать их вызовы и имена ресурсов. Регулярные ресурсы были найдены. Некоторых еще не было, хотя они там (подтверждено). Но кое-что, о чем я не знаю, хотя я старался учиться, - это множество вызовов с именами ресурсов, которые заканчиваются косой чертой. У некоторых также есть косые черты, где обычно появляется знак доллара (вложенные / внутренние ресурсы класса). Некоторые примеры, которые были запрошены, но НЕ найдены:
com / acme / foo / bar / ClassName / com / acme / foo / bar / ClassName / InnerClassName /
Когда я запускаю загруженный код со всем содержимым в начальном / загрузочном пути к классам (и не использую мой загрузчик классов), все работает нормально - таким образом, мой загрузчик классов ломает вещи, но мне нужно, чтобы это работало.
Мои самые близкие предположения:
Сторонний код каким-то образом получает истинный системный загрузчик, возможно, через какой-то класс, который был загружен им, а затем использует это. Я не вижу запросов к нему, и они обязательно потерпят неудачу, потому что у него нет полного пути к классу.
Этот бизнес с именами ресурсов, заканчивающимися косыми чертами, является причиной того, что он поддерживается истинным системным загрузчиком классов, но не поддерживается URLClassLoader, который я создаю. Я могу только догадываться, что ожидаемый возвращаемый URL как-то находит коллекцию ресурсов с этим именем в качестве префикса. Это было бы сложно, хотя возможно. Кроме того, кажется, что некоторые косые черты находятся в позициях, где должен быть знак доллара, разделяющий имя внутреннего класса, то есть в приведенном выше примере (пробелы добавлены для ясности):
com / acme / foo / bar / ClassName / InnerClassName /
com / acme / foo / bar / ClassName $ InnerClassName /
Обратите внимание, что я не могу рассчитывать на взлом реального загрузчика классов системы, предполагая, что он является подклассом URLClassLoader, и используя отражение для вызова его метода addURL(URL).
Есть ли способ сделать эту работу? Пожалуйста помоги!
ОБНОВИТЬ
Я только что сделал дополнительную попытку. Я создал фиктивный загрузчик классов-обёрток (расширяющий ClassLoader, а не URLClassLoader), который только регистрирует запросы, а затем передает их родителю (общедоступные методы) или суперклассу (защищенные методы). Я установил это как системный загрузчик классов и вручную добавил весь "внутренний" путь к классу к фактическому внешнему, затем попытался запустить код. Это работает правильно, так же, как и без пользовательского системного загрузчика классов. То, что было зарегистрировано, также указывало на то, что даже системный загрузчик класса возвращает ноль для этих ресурсов, заканчиваясь косой чертой для большинства из них, но не для всех. Я не проверял, работают ли они также в моем реальном коде, но предположил, что они могут - поскольку они не были камнем преткновения. Каким-то образом пользовательский системный загрузчик классов все еще игнорируется. Как?
ОБНОВЛЕНИЕ 2
В моих пользовательских загрузчиках системных классов я позволил некоторым классам исходить из внешнего / истинного системного загрузчика классов, например, из java.lang. Теперь я подозреваю, что не должен был и что внутренний "мир" должен быть полностью изолирован. Тем не менее, было бы проблематично общаться с ним, и все, что я бы оставил, это размышление... но не уверен, сработает ли это вообще - то есть может ли быть более одного java.lang.Class и / или java.lang.Object?
2 ответа
Учитывая все ограничения, это не кажется полностью возможным, как я хотел:
а) Сторонние библиотеки всегда могут "плохо себя вести" и завладеть загрузчиками, которые они не должны использовать так или иначе. Я посмотрел на OneJar как предложено fge, но у них та же проблема - они только обнаруживают возможность этого.
б) Нет способа полностью заменить / скрыть загрузчик системного класса.
c) Преобразование загрузчика системного класса в URLClassLoader может прекратить работу в любой момент.
Кажется, вы не поняли структуру загрузчика классов. В обычной JVM текущей версии есть как минимум три загрузчика классов:
- Загрузчик начальной загрузки, который отвечает за загрузку основных классов. Так как это включает в себя такие классы, как
Class
а такжеClassLoader
само по себе оно не может быть представленоClassLoader
пример. Все классы которыхgetClassLoader()
возвращаетсяnull
были загружены загрузчиком - Удлинитель-загрузчик. Он отвечает за загрузку классов в пределах
ext/
справочник JRE. Afaik, он может исчезнуть в будущих версиях. Его родительский загрузчик - загрузчик начальной загрузки - Загрузчик приложений. Это тот, который будет возвращен
ClassLoader.getSystemClassLoader()
и который будет использоваться, если не указан другой родитель. В текущих конфигурациях его родитель является загрузчиком расширений, но, возможно, он будет иметь загрузчик начальной загрузки в качестве прямого родителя в будущих версиях.
Вывод: если вы хотите перезагрузить классы вашего приложения без делегирования родительскому загрузчику, который разрушает ваши усилия, вам не нужно манипулировать реализацией загрузчика классов. Вам просто нужно указать правильного родителя. Это так же просто, как
URLClassLoader cl=new URLClassLoader(urls, ClassLoader.getSystemClassLoader().getParent());
Таким образом, родительский объект загрузчика нового класса будет исходным родительским загрузчиком класса приложения, поэтому уже загруженные классы приложений не входят в область действия нового загрузчика, в то время как все остальное работает как обычно.
Что касается ресурсов, заканчивающихся косой чертой, они довольно редки. Они могут быть решены, когда они фактически ссылаются на каталог, но это зависит от протокола URL и фактического обработчика для этого протокола. Например, это может работать для file:
URL, но обычно не для jar:
URL-адреса, если файл JAR не содержит псевдозаписей для каталогов. Я также видел, как это работает для ftp:
URL-адрес.
Другая вещь, которую нужно понять, это то, что если один класс напрямую ссылается на другой класс, будет запрашиваться его исходный определяющий загрузчик классов, а не обязательно загрузчик классов приложений. Например, когда класс java.lang.String
содержит ссылку на java.lang.Object
(это так), эта ссылка будет напрямую разрешена с помощью загрузчика начальной загрузки, так как это определяющий загрузчик java.lang.String
,
Это подразумевает, что если вы манипулируете родительским поиском загрузчика, чтобы не следовать стандартному родительскому делегированию, вы рискуете разрешить имена в разные классы времени выполнения в качестве разрешения тех же имен, когда на них ссылаются классы, загруженные родительским загрузчиком. Вы избегаете таких проблем, следуя стандартной процедуре, описанной выше. Классы JRE никогда не будут содержать ссылок на классы вашего приложения, и новый загрузчик, не имеющий исходного загрузчика приложения в качестве своего родителя, никогда не будет мешать классам, загруженным исходным загрузчиком приложения.