Search code examples
javaclassloader

Two Class Loaded by ClassLoader, They Can Not Call Each Other


Assume you have two java class

package package1;

public class SampleClass1 {

    public SampleClass1() {

        System.out.println("Sample Class Loaded and Called");
    }
}

package package1.package2;

import package1.SampleClass1;

public class SampleClass2 {

    public SampleClass2() {

        new SampleClass1();
        System.out.println("Sample Class 2 Loaded and Called");
    }
}

and load two classes by ClassLoader something like that:

package load;

public class LoadClassFromByteData extends ClassLoader {

    private byte[] classByteData;

    public LoadClassFromByteData(byte[] classByteData) {
        this.classByteData = classByteData;
    }

    public Class<?> getLoadedClass(String className) {

        return defineClass(className, classByteData, 0, classByteData.length);
    }
}

//

Class<?> loadedClass1 = loadClassFromByteData.getLoadedClass("package1.SampleClass1");
Class<?> loadedClass2 = loadClassFromByteData.getLoadedClass("package1.package2.SampleClass2");

when i call constructor of SampleClass1 like that:

Constructor<?> mainCons1 = loadedClass1.getConstructor();
mainCons1.newInstance();

successfully, printing "Sample Class Loaded and Called" but when i call constructor of loadedClass2 load from SampleClass2 byte data i have an error like:

Constructor<?> mainCons2 = loadedClass2.getConstructor();
mainCons2.newInstance();

// And yes, first i load SampleClass1 then i load SampleClass2, finally i call SampleClass2 constructor.

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at db.recorder.deneme.main(deneme.java:48)
Caused by: java.lang.NoClassDefFoundError: package1/SampleClass1
    at package1.package2.SampleClass2.<init>(SampleClass2.java:9)
    ... 5 more
Caused by: java.lang.ClassNotFoundException: package1.SampleClass1
    at java.base/java.lang.ClassLoader.findClass(ClassLoader.java:718)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 6 more

I want to add this information, two Classes byte data stored in DB, and i get byte data of classes from DB. Actually, the project has not any package named package1 or package2 !

Thanks for your answers and advices.


Solution

  • You need to supply the parent classloader.

       public static class LoadClassFromByteData extends ClassLoader {
    
            final ConcurrentHashMap<String, Class<?>> definedClassesByName = new ConcurrentHashMap<>();
    
            LoadClassFromByteData(ClassLoader parent) {
                super(parent);
            }
    
            public Class<?> getLoadedClass(String className, byte[] classAsBytes) {
    
                return this.definedClassesByName.computeIfAbsent(className,
                        cn -> defineClass(cn, classAsBytes, 0, classAsBytes.length));
            }
        }
    
        @Test
        public void test() throws IOException, InstantiationException, IllegalAccessException {
    
            final byte[] sampleClass1Bytes = Files.readAllBytes(SAMPLE_CLASS1_CLASS_FILE_PATH);
            final byte[] sampleClass2Bytes = Files.readAllBytes(SAMPLE_CLASS2_CLASS_FILE_PATH);
    
            final ClassLoader contextClassLoader = Thread.currentThread()
                .getContextClassLoader();
            final LoadClassFromByteData loadClassFromByteData = new LoadClassFromByteData(contextClassLoader);
            final Class<?> sampleClass1 = loadClassFromByteData.getLoadedClass("package1.SampleClass1", sampleClass1Bytes);
            final Object sampleClass1Instance = sampleClass1.newInstance();
            final Class<
                ?> sampleClass2 = loadClassFromByteData.getLoadedClass("package1.package2.SampleClass2", sampleClass2Bytes);
            final Object newInstance = sampleClass2.newInstance();
        }