Search code examples
javareflectionjavassist

javassist throws ClassNotFoundException when loading external classes from jar


I'm trying load an external jar file with javassist & call its main method at runtime, however when i try to do this with the below code:

    File file = new File("C:\\Users\\MainPC\\Desktop\\test.jar");
    ClassPool cp = ClassPool.getDefault();
    cp.insertClassPath(file.getAbsolutePath());

    Class<?> MainClass = cp.get("TestPackage.MainClass").toClass();
    MainClass.getMethod("main", String[].class).invoke(null, new Object[] {args});

It throws the following exception:

Exception in thread "main" java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at ReflectionTests.main(ReflectionTests.java:99)

Caused by: java.lang.NoClassDefFoundError: TestPackage/OtherClass
    at TestPackage.MainClass.main(Unknown Source)
    ... 5 more

Caused by: java.lang.ClassNotFoundException: TestPackage.OtherClass
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    ... 6 more

When i attempt the same thing only using the built in java reflection api it works with no problems:

    File file = new File("C:\\Users\\MainPC\\Desktop\\test.jar");
    URLClassLoader cl = new URLClassLoader(new URL[] {new URL("jar:file:"+file.getAbsoluteFile()+"!/")});
    
    Class<?> clazz = cl.loadClass("TestPackage.MainClass");
    clazz.getMethod("main", String[].class).invoke(null, new Object[] {args});

(the above throws no exceptions & calls the main method of the jar file as expected)

This leaves me to believe i'm doing something wrong in javassist (specifically with the loading of the jar file classes). Can someone explain to me what it is? I should mention the jar file only contains 2 classes: MainClass.class & OtherClass.Class. Both reside in a package called TestPackage. It seems the error has something to do with the MainClass class not being able to find the OtherClass class, when javassist loads it.


Solution

  • The problem was that when i call ct.toClass() it only exposes the class itself to my runtimes classloader (not the entire classpool's classpath). When i then later attempt to invoke the main method of this class, my runtimes classloader tries to execute the part that loads the other class which it obviously doesn't know about and so throws a ClassNotFoundException.

    The solution is to use the javassist provided classloader (javassist.Loader) which takes a classpool as argument in the constructor and then is able to load & resolve classes from the classpools classpath properly.

    Here's a working code example of what i was trying to achieve:

        File file = new File("C:\\Users\\MainPC\\Desktop\\test.jar");
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(file.getAbsolutePath());
    
        Loader loader = new Loader(cp);
        Class<?> MainClass = loader.loadClass("TestPackage.MainClass");
        MainClass.getMethod("main", String[].class).invoke(null, new Object[] {args});