【问题标题】:How do I programmatically compile and instantiate a Java class?如何以编程方式编译和实例化 Java 类?
【发布时间】:2011-02-26 03:25:32
【问题描述】:

我将类名存储在属性文件中。我知道类存储将实现 IDynamicLoad。如何动态实例化类?

我现在有

     Properties foo = new Properties();
    foo.load(new FileInputStream(new File("ClassName.properties")));
    String class_name = foo.getProperty("class","DefaultClass");
    //IDynamicLoad newClass = Class.forName(class_name).newInstance();

newInstance 是否只加载已编译的 .class 文件?如何加载未编译的 Java 类?

【问题讨论】:

标签: java reflection dynamic-loading


【解决方案1】:

如何加载未编译的 Java 类?

你需要先编译它。这可以使用javax.tools API 以编程方式完成。这只需要在 JRE 之上的本地计算机上安装 JDK

这是一个基本的启动示例(抛开明显的异常处理):

// Prepare source somehow.
String source = "package test; public class Test { static { System.out.println(\"hello\"); } public Test() { System.out.println(\"world\"); } }";

// Save source in .java file.
File root = new File("/java"); // On Windows running on C:\, this is C:\java.
File sourceFile = new File(root, "test/Test.java");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));

// Compile source file.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, sourceFile.getPath());

// Load and instantiate compiled class.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
Class<?> cls = Class.forName("test.Test", true, classLoader); // Should print "hello".
Object instance = cls.newInstance(); // Should print "world".
System.out.println(instance); // Should print "test.Test@hashcode".

哪个产量像

hello
world
test.Test@ab853b

如果这些类implements 某个接口已经在类路径中,则进一步使用会更容易。

SomeInterface instance = (SomeInterface) cls.newInstance();

否则您需要让Reflection API 参与来访问和调用(未知)方法/字段。


这与实际问题无关:

properties.load(new FileInputStream(new File("ClassName.properties")));

java.io.File 依赖于当前工作目录会导致可移植性问题。不要那样做。将该文件放在类路径中,并将ClassLoader#getResourceAsStream() 与类路径相对路径一起使用。

properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties"));

【讨论】:

  • 只是一个离题的问题。当我按照你的方式加载属性时我得到一个空值,但是当我这样做时我得到了属性 Foo.class.getResourceAsStream()?你能帮我理解你的代码吗?谢谢。
  • 该属性文件显然与Foo 类放在同一个包中。如前所述,您需要指定一个类路径相对路径,例如com/example/filename.properties。但是如果你能保证属性文件总是和Foo类在同一个包里,那么Class#getResourceAsStream()也是可以的。您只会错过将属性文件外部化到应用程序之外的功能,以便可以在不修改/重新打包应用程序的情况下对其进行修改。
  • @Raffi:我不小心用了 Guava 而不是 Java NIO,我已经修复了答案,谢谢!
  • @BalusC:对不起,如果我是多余的,但我可以在编译 java 文件之前指定类路径吗?如果是这样,将需要您的指导。
  • 是否可以在内存中编译类,将生成的字节码作为结果字节数组并将其存储在其他地方(例如在网络上或数据库中),以便自定义类加载器可以检索它? (而不是写本地文件)。
【解决方案2】:

与 BalusC 的答案相同,但在我的 kilim 发行版的这段代码中,这里有一个更自动的包装器。 https://github.com/kilim/kilim/blob/master/src/kilim/tools/Javac.java

它获取包含 Java 源代码的字符串列表,提取包和公共类/接口名称,并在 tmp 目录中创建相应的目录/文件层次结构。然后它在其上运行 java 编译器,并返回名称、类文件对(ClassInfo 结构)的列表。

帮助自己编写代码。它是 MIT 许可的。

【讨论】:

  • 编译失败会怎样?
【解决方案3】:

如果您知道该类具有公共无参数构造函数,则您的注释代码是正确的。您只需要转换结果,因为编译器无法知道该类实际上会实现IDynamicLoad。所以:

   IDynamicLoad newClass = (IDynamicLoad) Class.forName(class_name).newInstance();

当然,这个类必须被编译并在类路径上才能工作。

如果您希望从源代码动态编译一个类,那就另当别论了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-11
    • 1970-01-01
    • 2017-01-26
    • 2011-03-12
    • 1970-01-01
    • 1970-01-01
    • 2020-12-29
    相关资源
    最近更新 更多