Search code examples
javajvmclassloaderclasscastexceptiondynamic-class-loaders

ClassCastException while I use custom classloader


I try to understand java clasloading. I wrote example:

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);
        }
    }
}

also I have interface:

public interface ISexyInterface {

    public void makeBar ();

}

and class:

public class SexyClassForLoader implements ISexyInterface {
  ...
}

main method:

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();
    }

this example works fine and produces following output:

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

But lets play with:

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

If uncomment this line I see following output:

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)

Please, explain this behaviour.

P.S.

I understand that if same class loaded with 2 different loaders. It counted as 2 different classses.


Solution

  • If uncomment this line I see following output:......

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

    sur.che.SexyClassForLoader was loaded by Xloader and because it has an interface sur.che.ISexyInterface then jvm will use the classloader of sur.che.SexyClassForLoader to load this interface.
    But as for this statement:

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

    because the class which contained main method was loaded by AppLoader So jvm will use AppLoader to load ISexyInterface.class.

     ISexyInterface local_sexy = (ISexyInterface) sexy_ob;
    

    And obviously, the classLoader of this ISexyInterface is AppLoader, however the inner dependence (ISexyInterface) of SexyClassForLoader was loaded by xLoader. In other words, there are two ISexyInterface.class in jvm one loaded by Xloader another by App. And the sexy_ob implements the ISexyInterface.class loaded by Xloader. You try to convert sexy_ob into a ISexyInterface type(loaded by App). so castException.

    If commented, there is only one ISexyInterface .class which loaded by App . So everything is ok.