бросить через загрузчик классов?

Как я могу это сделать:

class Foo {
  public static Foo get() throws Exception {
    ClassLoader cl = new URLClassLoader(new URL[]{"foo.jar"}, null); // Foo.class is in foo.jar
    return (Foo)cl.loadClass("Foo").newInstance(); // fails on class cast
  }
}

Мне нужно, чтобы JVM рассматривала экземпляр Foo из cl, как если бы он был экземпляром Foo из загрузчика классов исполняемого кода.

Я видел эти подходы, ни один из них мне не подходит (приведенный выше пример - игрушечный пример):

  1. Загрузите класс (или отдельный интерфейс) с помощью загрузчика классов, который является родителем как вызывающего кода, так и созданного загрузчика классов.
  2. Сериализовать и десериализовать объект.

person IttayD    schedule 07.04.2010    source источник


Ответы (6)


Невозможно. Идентификатор класса состоит из полного имени и загрузчика класса.

Приведение объекта к классу с тем же именем, загруженному разными загрузчиками классов, ничем не отличается от попытки приведения String к Integer, потому что эти классы действительно могут быть совершенно разными, несмотря на то, что они имеют одно и то же имя.

person Michael Borgwardt    schedule 07.04.2010
comment
Итак, если мы не можем даже преобразовать эти новые экземпляры, как мы можем их использовать? Если все, что мы можем сделать, это Object obj = cl.loadClass("Foo").newInstance();, то как нам вызывать методы нового экземпляра Foo? - person Pacerier; 22.08.2014
comment
@Pacerier: ну, ты можешь использовать отражение. Но более практичным случаем является то, что классы расширяют классы или реализуют интерфейсы из родительского загрузчика классов в иерархии делегирования, которые также доступны для остального кода. Но в примере кода нет родительского загрузчика классов (второй параметр конструктора равен нулю)... - person Michael Borgwardt; 22.08.2014
comment
Будет ли производительность второго варианта выше, чем при использовании отражения? Или правда, что он все равно внутренне использует отражение? - person Pacerier; 23.08.2014
comment
@Pacerier: второй вариант работает точно так же, как классы из того же загрузчика классов, поэтому он быстрее, чем использование отражения; но в любом случае этот разрыв сокращается с каждой новой версией JVM. - person Michael Borgwardt; 23.08.2014

Я только что провел последние два дня, борясь с этой точной проблемой, и наконец решил проблему, используя отражение java:

// 'source' is from another classloader
final Object source = events[0].getSource();

if (source.getClass().getName().equals("org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread")) {

    // I cannot cast to 'org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread'
    // so I invoke the method 'terminate()' manually
    Method method = source.getClass().getMethod("terminate", new Class[] {});
    method.invoke(source, new Object[] {});
}

Надеюсь, это поможет кому-то.

person viragoboy    schedule 18.01.2013
comment
Как мы можем сделать это без рефлексии? Если каждый метод каждого отдельного объекта, загружаемого нашим загрузчиком классов, должен вызываться через отражение, не приведет ли это к серьезному торможению всей программы? - person Pacerier; 22.08.2014

Если класс, который необходимо привести, реализует Serializable, то:

private <T> T castObj(Object o) throws IOException, ClassNotFoundException {
    if (o != null) {
        ByteArrayOutputStream baous = new ByteArrayOutputStream();
        {
            ObjectOutputStream oos = new ObjectOutputStream(baous);
            try {
                oos.writeObject(o);
            } finally {
                try {
                    oos.close();
                } catch (Exception e) {
                }
            }
        }

        byte[] bb = baous.toByteArray();
        if (bb != null && bb.length > 0) {
            ByteArrayInputStream bais = new ByteArrayInputStream(bb);
            ObjectInputStream ois = new ObjectInputStream(bais);
            T res = (T) ois.readObject();
            return res;
        }
    }
    return null;
}

Применение:

Object o1; // MyObj from different class loader
MyObj o2 = castObj(o1);
person wit    schedule 20.10.2016
comment
Я просто хочу отметить, что использование сериализации эквивалентно глубокому копированию полей между объектами. Однако он не копирует тела методов. Таким образом, MyObj (A), загруженный основным загрузчиком классов, и MyObj (B), полученный из пользовательского загрузчика, отличаются друг от друга, и содержимое большинства полей совпадает. - person Vortex; 16.11.2016
comment
Это очень помогло мне решить мою проблему, когда я развертывал динамический веб-сервис... спасибо @wit - person Surajit Biswas; 30.07.2019

Невозможно использовать другой classLoader.

У вас есть этот обходной путь с Gson, пример приведения Object к YourObject (Object — это класс YourObject, но в другом classLoader):

Object o = ... 
Gson gson = new Gson();
YourObject yo = gson.fromJson(gson.toJson(o), YourObject.class);

Я использую этот обходной путь, потому что компилирую любой код Java в WebApp (на Tomcat). Этот обходной путь запущен в производстве.

person Stéphane GRILLON    schedule 13.01.2016
comment
Это блестяще! Это может быть хак, но он решил очень неприятную проблему с развертыванием для меня. - person wirefox; 23.08.2018

Возможно, вам подойдет что-то с использованием интерфейсов и java.lang.reflect.Proxy. Использование InvocationHandler, которое находит и вызывает соответствующий метод целевого класса. (Обратите внимание: если вы сделаете это, любой ваш мобильный код безопасности будет взломан.)

person Tom Hawtin - tackline    schedule 07.04.2010

Это старый пост, к которому я пришел, потому что хотел сделать почти то же самое, но в более простой версии...

На самом деле это работает, если и Foo, и загруженный класс (в моем случае из файла класса .java в другом пакете) расширяют один и тот же класс, скажем, AbstractTestClass.

Фрагменты кода:

public AbstractTestClass load(String toLoad) {
    try{
        Class test = Class.forName("testsFolder.testLoadable");
        Constructor ctorlist[] = test.getDeclaredConstructors();
        for(Constructor aConstructor : ctorlist){
           if(...){// find the good constructor
              Object loadedTest = aConstructor.newInstance(new Object[]{/*params*/});
              return (AbstractTestClass) test;
           }
        }
    }catch(...){}
    return new defaultTestClass();
}

Таким образом я могу вставить загруженный класс в файл ArrayList<AbstractTestClass>.

person Aname    schedule 25.04.2015
comment
вы загружаете «тест» из того же загрузчика классов. Кроме того, даже если вы укажете другой, он должен быть дочерним по отношению к текущему загрузчику классов, иначе вы не сможете выполнить приведение к AbstractTestClass. - person IttayD; 26.04.2015
comment
@IttayD, вы правы, ему нужно расширить родительский класс (Foo в вопросе). Разве не в этом был смысл вопроса: рассматривать экземпляр Foo из cl как экземпляр Foo? - person Aname; 18.05.2015