从jar加载外部类时,javassist引发ClassNotFoundException

问题描述

我正在尝试使用javassist加载外部jar文件并在运行时调用其main方法,但是当我尝试使用以下代码进行此操作时:

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

它引发以下异常:

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

当我仅使用内置的Java反射api尝试相同的操作时,它没有任何问题:

    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",new Object[] {args});

(上面没有抛出异常,并按预期方式调用了jar文件的main方法

这使我相信我在javassist中做错了(特别是在加载jar文件类时)。有人可以告诉我这是什么吗? 我应该提到jar文件仅包含2个类:MainClass.class和OtherClass.Class。两者都驻留在一个名为TestPackage的程序包中。似乎该错误与javassist加载MainClass类时无法找到OtherClass类有关。

解决方法

问题在于,当我调用ct.toClass()时,它仅将类本身公开给我的运行时类加载器(而不是整个类池的类路径)。然后,当我稍后尝试调用此类的主要方法时,我的运行时类加载器将尝试执行加载显然不知道的另一个类的部分,并因此引发 ClassNotFoundException 。>

解决方案是使用javassist提供的类加载器(javassist.Loader),该类加载器将类池作为构造函数中的参数,然后能够从类池的类路径中正确加载和解析类。

这是我要实现的工作代码示例:

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