没有办法使用ClassLoader
作为参考,除非它能够提供其定义类的类字节。也就是说,如果你有一个Class
代表顶级类的实例,您可以使用classInstance.getResourceAsStream(classInstance.getSimpleName()+".class")
尝试获取类字节。如果您有权访问构成动态类的字节,则可以通过 java 编译器将它们提供给JavaFileManager https://docs.oracle.com/javase/8/docs/api/?javax/tools/JavaFileManager.html执行。
编译器 API 是标准 API 的一部分,不需要第三方库。以下代码通过首先编译测试类,然后根据上一步中刚刚创建的类设置必要的环境来编译第二个类来演示这一点:
// customize these, if you want, null triggers default behavior
DiagnosticListener<JavaFileObject> diagnosticListener = null;
Locale locale = null;
// the first class, to be present at runtime only
String class1 = "package test;\npublic class Class1 {}";
JavaCompiler c = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm
= c.getStandardFileManager(diagnosticListener, locale, Charset.defaultCharset());
// define where to store compiled class files - use a temporary directory
fm.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(
Files.createTempDirectory("compile-test").toFile()));
JavaCompiler.CompilationTask task = c.getTask(null, fm,
diagnosticListener, Collections.emptySet(), Collections.emptySet(),
Collections.singleton(new SimpleJavaFileObject(
URI.create("string:///Class1.java"), Kind.SOURCE) {
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return class1;
}
}));
if(task.call()) {
FileObject fo = fm.getJavaFileForInput(
StandardLocation.CLASS_OUTPUT, "test.Class1", Kind.CLASS);
// these are the class bytes of the first class
byte[] class1bytes = Files.readAllBytes(Paths.get(fo.toUri()));
// the actual task: define a class dependent on the first class
String class2 = "package test;\npublic class Class2 { Class1 variable; }";
// create a file object representing the dynamic class
JavaFileObject jo = new SimpleJavaFileObject(
URI.create("runtime:///test/Class1.class"), Kind.CLASS) {
@Override public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(class1bytes);
}
};
// and a custom file manager knowing how to locate that class
JavaFileManager myFM = new ForwardingJavaFileManager(fm) {
@Override
public JavaFileObject getJavaFileForInput(
JavaFileManager.Location location, String className, Kind kind)
throws IOException {
if(location==StandardLocation.CLASS_PATH&&className.equals("test.Class1")) {
return jo;
}
return super.getJavaFileForInput(location, className, kind);
}
@Override
public boolean hasLocation(JavaFileManager.Location location) {
return location==StandardLocation.CLASS_PATH || super.hasLocation(location);
}
@Override
public Iterable list(JavaFileManager.Location location,
String packageName, Set kinds, boolean recurse) throws IOException {
if(location==StandardLocation.CLASS_PATH
&& (packageName.equals("test") || recurse&&packageName.isEmpty())) {
return Collections.singleton(jo);
}
return super.list(location, packageName, kinds, recurse);
}
@Override
public String inferBinaryName(
JavaFileManager.Location location, JavaFileObject file) {
if(file==jo) return "test.Class1";
return super.inferBinaryName(location, file);
}
};
// compile the second class using the custom file manager to locate dependencies
task = c.getTask(null, myFM,
diagnosticListener, Collections.emptySet(), Collections.emptySet(),
Collections.singleton(new SimpleJavaFileObject(
URI.create("string:///Class2.java"), Kind.SOURCE) {
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return class2;
}
}));
if(task.call()) {
fo = fm.getJavaFileForInput(
StandardLocation.CLASS_OUTPUT, "test.Class2", Kind.CLASS);
// there we have the compiled second class
byte[] class2bytes = Files.readAllBytes(Paths.get(fo.toUri()));
}
}
当然,这只是为了演示原理。您肯定想为文件对象创建工厂方法并使用Map
s 用于记住它们等等。
还可以用自定义内存存储替换临时目录。但关键点仍然是,编译器需要能够访问类字节。它不会使用加载的运行时类。