ClassCastException, пока я использую пользовательский загрузчик классов

Я пытаюсь понять Java-классификацию. Я написал пример:

class XLoader extends ClassLoader {

    // карта отображения имен классов на файлы .class, где хранятся их определения
    HashMap<String, String> mappings;

    XLoader(HashMap mappings) {
        this.mappings = mappings;
    }


    public synchronized Class loadClass(String name) throws ClassNotFoundException {
        try {
            // важно!
            // приоритет отдан именно загрузке с помощью встроенного загрузчика

            if (!mappings.containsKey(name)) {
                System.out.println("loadClass (" + name + ") with parent classloader");
                return super.findSystemClass(name);
            }
            System.out.println("loadClass (" + name + ") with XLoader");
            String fileName = mappings.get(name);
            FileInputStream fin = new FileInputStream(fileName);
            byte[] bbuf = new byte[(int) (new File(fileName).length())];
            fin.read(bbuf);
            fin.close();
            return defineClass(name, bbuf, 0, bbuf.length);

        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException(e.getMessage(), e);
        }
    }
}

также у меня есть интерфейс:

public interface ISexyInterface {

    public void makeBar ();

}

и класс:

public class SexyClassForLoader implements ISexyInterface {
  ...
}

основной метод:

public static void main(String[] args) throws Exception {

        HashMap<String, String> mappings = new HashMap();
        mappings.put("sur.che.SexyClassForLoader", "D:\\java_things\\custom-classloader\\out\\production\\custom-classloader1\\sur\\che\\SexyClassForLoader.class");
        // if comment this line you will see
        //mappings.put("sur.che.ISexyInterface", "D:\\java_things\\custom-classloader\\out\\production\\custom-classloader1\\sur\\che\\ISexyInterface.class");

        XLoader xloa = new XLoader(mappings);
        Class sexy_cla = xloa.loadClass("sur.che.SexyClassForLoader");
        System.out.println("class was loaded with " + sexy_cla.getClassLoader());

        Object sexy_ob = sexy_cla.newInstance();
        System.out.println(sexy_ob.getClass().getClassLoader());
        System.out.println(ISexyInterface.class.getClassLoader());
        Thread.sleep(100);
        ISexyInterface local_sexy = (ISexyInterface) sexy_ob;
        local_sexy.makeBar();
    }

этот пример работает нормально и выдает следующий вывод:

loadClass (sur.che.SexyClassForLoader) with XLoader
loadClass (sur.che.ISexyInterface) with parent classloader
loadClass (java.lang.Object) with parent classloader
class was loaded with sur.che.XLoader@7f31245a
loadClass (java.lang.System) with parent classloader
loadClass (java.io.PrintStream) with parent classloader
SexyClassForLoader$$static
SexyClassForLoader$$init
sur.che.XLoader@7f31245a
sun.misc.Launcher$AppClassLoader@232204a1
make bar

Но давайте поиграем с:

    //mappings.put("sur.che.ISexyInterface", "D:\\java_things\\custom-classloader\\out\\production\\custom-classloader1\\sur\\che\\ISexyInterface.class");

Если раскомментировать эту строку, я вижу следующий вывод:

loadClass (sur.che.SexyClassForLoader) with XLoader
loadClass (sur.che.ISexyInterface) with XLoader
loadClass (java.lang.Object) with parent classloader
class was loaded with sur.che.XLoader@7f31245a
loadClass (java.lang.System) with parent classloader
loadClass (java.io.PrintStream) with parent classloader
SexyClassForLoader$$static
SexyClassForLoader$$init
sur.che.XLoader@7f31245a
sun.misc.Launcher$AppClassLoader@232204a1
Exception in thread "main" java.lang.ClassCastException: sur.che.SexyClassForLoader cannot be cast to sur.che.ISexyInterface
    at sur.che.Loader.main(Loader.java:25)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Пожалуйста, объясните это поведение.

PS

Я понимаю, что если один и тот же класс загружается с 2 разными загрузчиками. Это считалось 2 разных классов.

1 ответ

Решение

Если раскомментировать эту строку, я вижу следующий вывод:......

Class sexy_cla = xloa.loadClass("sur.che.SexyClassForLoader");

sur.che.SexyClassForLoader был загружен Xloader, и поскольку он имеет интерфейс sur.che.ISexyInterface, тогда jvm будет использовать загрузчик классов sur.che.SexyClassForLoader для загрузки этого интерфейса.
Но что касается этого утверждения:

System.out.println(ISexyInterface.class.getClassLoader());

потому что класс, который содержал метод main, был загружен AppLoader. Таким образом, jvm будет использовать AppLoader для загрузки ISexyInterface.class.

 ISexyInterface local_sexy = (ISexyInterface) sexy_ob;

И, очевидно, classLoader этого ISexyInterface является AppLoader, однако внутренняя зависимость (ISexyInterface) SexyClassForLoader была загружена xLoader. Другими словами, в jvm естьдва ISexyInterface.class, один загружается Xloader, а другой - App. И sexy_ob реализует класс ISexyInterface.class, загружаемый Xloader. Вы пытаетесь преобразовать sexy_ob в тип ISexyInterface(загружается приложением). так что castException.

Если это прокомментировано, есть только один ISexyInterface.class, который загружается приложением. Так что все в порядке.

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