使用URLClassLoader加载.class文件

4

我知道这个问题之前已经被问过:

如何使用URLClassLoader加载*.class文件?

然而,由于缺乏示例,我并不是很理解。我目前正在开发一个项目,并尝试加载用户提供的.class对象,这些对象可以位于机器上的任何目录中。

//Create URL to hash function class file
URL url_hashFunctionPath = new URL("file:///" + _sHashFunctionFilePath);

//Packet URL to a URL array to be used by URLClassLoader
URL[] urlA_hashFunctionPath = {url_hashFunctionPath};

//Load URL for hash function via a URL class loader
URLClassLoader urlCl_hashFunctionClassLoader = new URLClassLoader(urlA_hashFunctionPath);

//Load user hash function into class to initialize later (TEST: HARD CODE FOR NOW)
m_classUserHashClass = urlCl_hashFunctionClassLoader.loadClass(_sUserHashClassName);

最后一行给了我一个ClassNotFoundException,从我的实验和理解来看,用户提供的类函数必须在类路径中吗?
PS:第一次发布问题,请随意纠正我未遵循适当方式的地方。
//解决方案
我通过[WillShackleford][1]的慷慨帮助得出的解决方案是,在给定的文件路径中加载.class文件。有关更多信息,请参阅代码及其给定的注释。
//The absolute file path to the class that is to be loaded (_sHashFunctionFilePath = absolute file path)
String pathToClassFile = _sHashFunctionFilePath;
System.out.println("File to class: " + _sHashFunctionFilePath);

//Declare the process builder to execute class file at run time (Provided filepath to class)
ProcessBuilder pb = new ProcessBuilder("javap", pathToClassFile);
try
{
    //Start the process builder
    Process p = pb.start();

    //Declare string to hold class name
    String classname = null;
    //Declare buffer reader to read the class file & get class name
    try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())))
    {
        String line;
        while(null != (line = br.readLine()))
        {
            if(line.startsWith("public class"))
            {
                classname = line.split(" ")[2];
                break;
            }
        }
        System.out.println("classname = " + classname);
    }
    catch(IOException _error)
    {

    }

    //Declare file path to directory containing class to be loaded
    String pathToPackageBase = pathToClassFile.substring(0, pathToClassFile.length() - (classname + ".class").length());
    System.out.println("pathToPackageBase = " + pathToPackageBase);

    try
    {
        //Create class to hold the class to be loaded via a URL class loader
        Class clss = new URLClassLoader(
                new URL[]{new File(pathToPackageBase).toURI().toURL()}
        ).loadClass(classname);

        //Create ab object/instance of said class
        Object test = clss.newInstance();

        //Declare & create requested method from user hash function class (Method is work & takes no arguments)
        Method method = clss.getMethod("work", null);
        method.invoke(test, null);
    }

URL.toURI().toURL() 的意义是什么? - Andreas
帮不了你。我们不知道_sUserHashClassName是什么。同样,我们也不知道_sHashFunctionFilePath是什么。 - Andreas
抱歉,我本意是使用file.toURI().toURL(),我混淆了思维,以为直接使用toURL()是一种已弃用的方式。 - Last
_sUserHashClassName 是类文件的名称(例如:Test.Class,名称为 Test)_sHashFunctionFilePath 是 .class 文件的绝对文件路径。 - Last
显示剩余2条评论
2个回答

5
在目录 /home/shackle/somedir/classes/pkg 中,我有一个文件 Test.class,它是由一个带有 package pkg; 的java文件创建的。
package pkg;

public class Test {

    public String toString() {
        return "secret_string";
    }
}

然后我使用以下代码进行加载:

System.out.println(new URLClassLoader(
        new URL[]{new File("/home/shackle/somedir/classes").toURI().toURL()}
).loadClass("pkg.Test").newInstance().toString());

请注意,我在URL字符串中没有放置pkg/Test,但是加载类参数具有pkg.前缀。
您可以通过以下方式直接从文件中获取类名:
Class clsReaderClss = ClassLoader.getSystemClassLoader().loadClass("jdk.internal.org.objectweb.asm.ClassReader");
System.out.println("clsReaderClss = " + clsReaderClss);
Constructor con = clsReaderClss.getConstructor(InputStream.class);
Object reader = con.newInstance(new FileInputStream(directFile));
Method m = clsReaderClss.getMethod("getClassName");
String name = m.invoke(reader).toString().replace('/', '.');
System.out.println("name = " + name);

一种不需要访问内部类的替代方法。
String pathToClassFile = "/home/shackle/somedir/classes/pkg/Test.class";
ProcessBuilder pb = new ProcessBuilder("javap",pathToClassFile);
Process p = pb.start();
String classname = null;
try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
   String line;
   while(null != (line = br.readLine())) {
       if(line.startsWith("public class")) {
           classname = line.split(" ")[2];
           break;
       }
   }
}
System.out.println("classname = " + classname);

可以使用以下方式加载类:

String pathToPackageBase = pathToClassFile.substring(0, pathToClassFile.length() - (classname + ".class").length());
System.out.println("pathToPackagBase = " + pathToPackageBase);
Class clss = new URLClassLoader(
        new URL[]{new File(pathToPackageBase).toURI().toURL()}
).loadClass(classname);
System.out.println(clss.newInstance().toString());

我理解,但这是否意味着我需要明确知道Test类所使用的包名?(因为我假设我的用户提供类文件,所以我不应该知道他/她正在使用的包名,或者我的有关包的理解在某些地方是错误的吗?) - Last
谢谢,我会研究一下的。从两个当前的答案中可以看出,这个问题比表面上看起来要复杂得多。等我完成研究后再回复你。 - Last
谢谢,但我能确认一下这行代码是干什么的吗?.getSystemClassLoader().loadClass("jdk....ClassReader"); - Last
所以,您需要的JDK是一个外部库,只需要在“导入”方面安装为外部库?如果需要在其他机器上运行,则是否需要将文件添加到我的项目中以供未来编译? - Last
嗨Will,我发布了对我有效的解决方案,并根据我的理解添加了注释。请随意编辑解决方案,或者告诉我,我可以为你做出修改。非常感谢你耐心的帮助,让我在Stack Overflow上的初次提问变得愉快。干杯! - Last
显示剩余2条评论

0

你的_sHashFunctionFilePath需要将目标类的包名从中删除,这样ClassLoader才能在_sHashFunctionFilePath + package.name + HashFunction.class中查找文件路径。如果不这样做,ClassLoader将无法找到该文件。

因此,如果目标类是my.great.HashFunction,则它需要位于名为my/great/的目录中,如果您想使用URLClassLoader。然后,如果实际上在/path/to/my/great/HashFunction.class中找到了.class文件,则可以使用/path/to作为URLClassLoader的file:/// URL。


抱歉,我看到很多例子都提到了数据包,并使用类似于您的示例“my.great.HashFunction”的符号表示法,我担心我不理解这些数据包的目的。(因为我假设我的用户提供类文件,所以我不应该知道他/她正在使用的包名称,或者我的关于包的理解是否存在误导?) - Last
如果您无法预测包名(在Java中,“package”一词具有明确的含义),则无法使用“ClassLoader.loadClass”来获取类。相反,您将不得不调用“ClassLoader.defineClass”和“ClassLoader.resolveClass”,这可能会让您感到棘手,因为这些方法是受保护的。您还必须事先知道类的名称,因为您必须知道类的名称才能定义它。您可以尝试使用BCEL之类的工具从类的原始字节中获取元数据,然后将其提供给“ClassLoader.defineClass”。 - Christopher Schultz
谢谢,我会研究一下。从两个当前的答案中可以看出,这个问题比表面上看起来要复杂得多。等我完成研究后再回复你。 - Last
你最好从贡献者那里加载.jar文件,而不是直接尝试加载.class文件。这样,JAR文件将包含所有必要的结构,甚至可以扫描JAR文件中包含的类,并且...我不知道,也许可以决定加载哪个类。 - Christopher Schultz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接