Как контролировать, какой ClassLoader загружает класс?
ситуация под рукой не так проста,как кажется, указывает название.
Java 1.6_17 работает через JWS.
у меня есть класс, скажем MyClass
и одна из его переменных-членов экземпляра-это тип из ошибочной сторонней библиотеки, где во время инициализации класса он динамически пытается загрузить некоторые из своих собственных классов с Class.forName(String)
. В одном из этих случаев происходит динамический вызов:Class.forName("foo/Bar")
.Это имя класса не следует за JLS для двоичных имен и в конечном счете приводит к java.lang.NoClassDefFoundError: foo/Bar
.
у нас есть такой обычай ClassLoader
что я добавил метод дезинфекции в ClassLoader.findClass(String)
и ClassLoader.loadClass(String)
что устраняет эту проблему.
я могу называть вещи, как:
myCustomClassLoader.findClass("foo/Bar")
, который затем загружает класс без каких-либо проблем. Но даже если я загружу класс раньше времени, я все равно получу исключение позже. Это связано с тем, что во время инициализации MyClass
Что относится к Bar
- их код заканчивается вызовом Class.forName("foo/Bar")
в a статический блок где-то. Это было бы нормально, если бы загрузчик классов, который он пытался использовать, был моим пользовательским загрузчиком классов. Но это не так. Это com.sun.jnlp.JNLPClassLoader
который не делает такой санитарии, поэтому моя проблема.
я убедился, что Thread.currentThread().getContextClassLoader()
установлен в мой пользовательский загрузчик класса. Но это (как вы знаете) не имеет никакого эффекта. Я даже установил его как первое, что я делаю в main()
из - за некоторых вещей, которые я читал и до сих пор,MyClass.class.getClassLoader()
- это JNLPClassLoader. Если бы я мог заставить его не быть JNLPClassLoader и использовать мой вместо этого, проблема решена.
как я могу контролировать, какой ClassLoader используется для загрузки класса через их статический класс.forName ("foo/Bar") вызов, сделанный во время инициализации класса? Я верю, если смогу заставить MyClass.class.getClassLoader()
чтобы вернуть мой пользовательский загрузчик классов, моя проблема будет решена.
я открыт для других вариантов, если у кого есть идеи.
TL; DR: помогите мне заставить всех Class.forName(String)
вызовы в сторонней библиотеке, на которые ссылается MyClass
- использовать загрузчик классов по моему выбору.
3 ответов
это напоминает мне статью, которую я читал 10 лет назад о механизмах загрузки классов в Java. Он все еще там на JavaWorld.
статья не ответит на ваш вопрос напрямую, но это может помочь понять вашу проблему. Вы должны вызвать MyClass
для загрузки через пользовательский загрузчик классов и trump поведение загрузки класса по умолчанию, которое заключается в том, чтобы сначала делегировать загрузку класса родительскому загрузчику классов и только попытаться загрузить класс, если это неудачи.
позволяет MyClass
чтобы загрузить загрузчик классов, отличный от вашего, будет хранить отношение из экземпляра класса к этому загрузчику классов (через getClassLoader
) и заставить Java использовать этот другой загрузчик классов, чтобы попытаться обнаружить любые ссылочные классы найден во время компиляции, эффективно минуя пользовательский загрузчик классов в силу иерархии загрузчика классов и модели делегирования. Если MyClass
вместо определена ваш загрузчик Class, вы получаете второй шанс.
это звучит как работа для чего-то вроде URLClassLoader
переопределение loadClass
и trumping модель делегирования для классов, проживающих в ваших банках. Вероятно, вы захотите использовать подход bootstrap (как предложил Томас в комментарии выше), чтобы заставить один класс entrypoint загружаться через ваш пользовательский загрузчик классов, перетаскивая с ним все остальные.
также информативным является эта другая статья JavaWorld тем же парнем, который предупреждает вас о предостережениях класса.forName. Это тоже может помешать вашей загрузке классов.
Я надеюсь, что это поможет и окажется информативным. В любом случае, это звучит как сложное решение, которое легко сломать по мере развития вашего кода.
Если у вас закончились идеи с исправлением ClassLoader
s сами, вы можете рассмотреть возможность перезаписи самого байт-кода библиотеки - просто замените константу" foo/bar " правильным значением, а затем вам не нужно настраивать дальнейшую загрузку классов вообще!
вы можете сделать это либо во время выполнения, либо заранее.
Я думаю, что все дали хорошие твердые попытки ответить на проблему. Однако оказалось,что я неправильно поставил диагноз.
Я попросил коллегу взять на себя проблему и попросил его получить JDK с флагами отладки, чтобы мы могли отладить JNLPClassLoader
чтобы увидеть, что происходит, поскольку я пробовал все предложения здесь + некоторые.
мы закончили тем, что получили OpenJDK, потому что перекомпиляция JDK с нуля-это полный кошмар (мы пытались). После получения OpenJDK, работающего с наш продукт и отладка через JNLPClassLoader
- оказывается, он все еще использовал очень старый .jnlp месяцами ранее, у которого был неправильный путь к ресурсам и, следовательно, почему он не мог найти класс.
мы были смущены, почему он все еще использует древний .jnlp, хотя мы правильно перераспределили сервер много раз с правильным .jnlp и множество изменений кода, между которыми были отражены в нашем клиентском приложении при запуске.
Ну, оказывается, что на клиентские машины, Java кэширует .файл jnlp. Даже если ваше приложение изменится и оно повторно загрузит ваше приложение, оно все равно не будет повторно загружать новое .jnlp по какой-то причине. Таким образом, он будет использовать весь новый код, но искать пути ресурсов/классов с помощью кэширования .у jnlp.
Если вы запустите:
javaws -uninstall
На клиентской машине, тогда будет ясно .кэш jnlp и в следующий раз он будет использовать правильно .файл jnlp.
очень грустно, что это было проблемой. Надеюсь, это спасет кто-то другой бесконечные часы разочарования, как это вызвало нас.