my site map

Wednesday, June 23, 2010

Java аномальное поведение, при получении ClassLoader`а


Работая над одним проектом, наткнулся на интересное поведение ObjectInputStream jvm от Sun/Oracle.
Надеюсь все знают что в джава классы загружаются при помощи ClassLoader`а, специального класса такого. При запуске приложения и, соответственно, потока к нему (потоку) прикрепляется его ClassLoader, который можно получить/изменить при помощи Thread.currentThread().set/getContextClassLoader. Если хотите можно конечно использовать свой ClassLoader либо напрямую вызывая loadClass, далее получая объект загруженного класса и вызывая его методы либо Thread.currentThread().setContextClassLoader(myClassLoader).
Это вроде как работает, но оказывается есть ситуации когда нельзя понять какой ClassLoader будет использоваться.


Начну наверное с того как я на это наткнулся.
Есть веб приложение с поддержкой плагинов. Плагины хранятся в user.home/./plugins. Таким образом плагины не находятся classpass приложения, для загрузки классов плагинов есть специальный ClassLoader, который умеет это делать.
Один из таких плагинов может сериализовать/десериализовать некоторые объекты на диск. В одном классе я неожиданно для себя получил ClassNotFoundException во время чтения объектов из ObjectInputStream.

Не буду вдаваться в подробности, exception получил при следующем коде:
...
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(someFile));
Object obj = ois.readObject();
...

Стектрейс получил следующий:
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1516)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1361)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:247)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:604)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1575)
...

Из стектрейса понял что используется нетот ClassLoader при загрузке класса модуля. Я удивился и попробовал перед вызовом метода установить тот ClassLoader что нужен был:
Thread.currentThread().setContextClassLoader(myClassLoader). И опять получил ClassNotFoundException. Вот *ля подумал я, что за дела и начал гуглить/смотреть javadoc/тацневать с бубном/и т.д.

Так же видно, что exception выкидывает ObjectInputStream.resolveClass. Я открыл javadoc на этот метод и увидел следующее:
...
The default implementation of this method in ObjectInputStream returns the result of calling

Class.forName(desc.getName(), false, loader)
 
where loader is determined as follows: if there is a method on the current thread's stack whose declaring class was defined by a user-defined class loader (and was not a generated to implement reflective invocations), then loader is class loader corresponding to the closest such method to the currently executing frame; otherwise, loader is null.
...
Так же на forums.sun.com прочитал, что данный метод использует 
native latestUserDefinedLoader()
и не понятно что значит описание javadoc ObjectInputStream.resolveClass метода.
Почитал несколько разделов форума forums.sun.com, там люди пришли к решению унаследовать ObjectInputStream и переопределить resolveClass. Да это решение будет работать, но что делать, если нет возможности этого сделать, например я использую стороннюю библиотеку, внутри которой всё это происходит.

Во дела, тупик! о0 либо я что-то очень важное упустил?

з.ы. мой первый пост, так что приветствую любые комментарии.

2 comments:

  1. Хм... интересная проблемка :)

    А что если попробовать использовать свой класс-лоадер самым первым для всего веб-приложения? Если же это сделать нельзя, и все это происходит в сторонней библиотеке - тогда придется лезть туда и подменять методы или классы бинутилсами. Где-то же создается ObjectInputStream, кто-то же далает ему new - значит можно и зацепиться за этот метод. Решение, конечно, не из простых и не из самых может даже нормальных - но первое, что пришло в голову :)

    ReplyDelete
  2. Спасибо за совет. Но пока что "игра не стоит свеч" для выделения времени на эту проблему, по позже может попробую.
    Кстати, всё это находится в сторонней библиотеке в моём случае :) и "хачить" её пока не очень то хочется.
    Противно, что проблема то распространённая оказалась для той библиотеки, а её разработчики не очень то активно отвечают на письма (

    ReplyDelete