我正在运行时生成.java
类文件,需要立即在代码中利用这些类。
因此,我使用Compiler API编译了.java
类以制作.class
文件:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
StandardJavaFileManager manager = compiler.getStandardFileManager(diagnostics, null, null);
File file = new File("path to file");
Iterable<? extends JavaFileObject> sources = manager.getJavaFileObjectsFromFiles(Arrays.asList(file));
CompilationTask task = compiler.getTask(null, manager, diagnostics, null, null, sources);
task.call();
manager.close();
然后我需要使用Class.forName()
获取对这些编译类的引用,但是如果我只调用Class.forName("com.foo.Bar")
则会抛出ClassNotFoundException
,这是因为新的.class
文件是没有添加到classpath
中,我正在寻找在运行时将类添加到classpath
的方法。我遇到了与此概念相关的一些歧义:
1。。这种方法(首先使用编译器API编译.java
文件,然后在第二步将其添加到类加载器)是否正确?为了能够立即使用代码中的类。
2。 AFAIK,有两种方法可以在运行时将类动态加载到类路径中:一种是使用这样的自定义ClassLoader :(我抱怨编译时出错,{{1 }}没有BuiltinClassLoader
方法):
addURL
另一种方法是使用 // Get the ClassLoader class
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> clazz = cl.getClass();
// Get the protected addURL method from the parent URLClassLoader class
Method method = clazz.getSuperclass().getDeclaredMethod("addURL", new Class[] { URL.class });
// Run projected addURL method to add JAR to classpath
method.setAccessible(true);
method.invoke(cl, new Object[] { cls });
将类添加到类路径(它也同时提供类引用)。如上所述,我遇到了编译器错误(Java 11),因此无法应用第一种方法。
关于第二种方法,如果我们这样调用默认的类加载器,Class.forName(name, instantiation, classLoader)
会将新类附加到Class.forName(name, instantiation, classLoader)
吗? :
classpath
这对我不起作用。上面的classLoader参数的哪个变体
是正确的,为什么这些都不起作用?创建自定义类加载器并将其传递给Class.forName("com.foo.Bar",true, ClassLoader.getSystemClassLoader());
// or:
Class.forName("com.foo.Bar",true, ApiHandler.class.getClassLoader());
是否很必要?
3。。我正在Eclipse项目的Class.forName()
文件夹中的.java
包中创建com.foo
文件。它们的已编译src
文件也在同一文件夹中生成(使用编译器API)。当我使用eclipse刷新项目时(右键单击项目->刷新),相关的.class
文件将在.class
文件夹中生成,这时可以通过代码访问类(例如,使用target/classes
。如果我通过Class.forName("com.foo.Bar)
文件夹生成.class
文件(通过编译器API),是否可以识别这些类而无需将其引入类路径?>
更新:
通过将受尊敬的target/classes
文件保存在项目的.class
文件夹中(如上面第三个问题所述),我可以在代码中使用编译的类。 (通过在编译器的target/classes
方法中添加-d
选项:
getTask()
这样,似乎甚至不需要使用classLoader将类添加到类路径中;因为可以使用简单的Iterable<String> options = Arrays.asList( new String[] { "-d", System.getProperty("user.dir") + "/target/classes/"} );
.
.
.
CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
访问该类。 您如何解释?
Class.forName()
当然也可以通过ClassLoader方式:
Class<?> cls1 = Class.forName("com.foo.Bar");
答案 0 :(得分:0)
最安全的解决方案是创建一个新的ClassLoader
实现,并通过新的加载器加载生成的类,如this answer所示。
但是从Java 9开始,如果尚未定义/加载具有该名称的类,则可以在自己的上下文中定义类,即在同一包中定义类。如上所述,这样的类定义甚至可以取代类路径上的定义,只要尚未加载它即可。因此,不仅随后的Class.forName(String)
调用都将被解析为此类定义,甚至非反射引用也将被解析。
这可以通过以下程序进行演示。
class Dummy { // to make the compiler happy
static native void extensionMethod();
}
public class CompileExtension {
public static void main(String[] args) throws IOException, IllegalAccessException {
// customize these, if you want, null triggers default behavior
DiagnosticListener<JavaFileObject> diagnosticListener = null;
Locale locale = null;
// the actual class implementation, to be present at runtime only
String class1 =
"class Dummy {\n"
+ " static void extensionMethod() {\n"
+ " System.out.println(\"hello from dynamically compiled code\");\n"
+ " }\n"
+ "}";
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,
Set.of(Files.createTempDirectory("compile-test").toFile()));
JavaCompiler.CompilationTask task = c.getTask(null, fm,
diagnosticListener, Set.of(), Set.of(),
Set.of(new SimpleJavaFileObject(
URI.create("string:///Class1.java"), JavaFileObject.Kind.SOURCE) {
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return class1;
}
}));
if(task.call()) {
FileObject fo = fm.getJavaFileForInput(
StandardLocation.CLASS_OUTPUT, "Dummy", JavaFileObject.Kind.CLASS);
// these are the class bytes of the first class
byte[] classBytes = Files.readAllBytes(Paths.get(fo.toUri()));
MethodHandles.lookup().defineClass(classBytes);
Dummy.extensionMethod();
}
}
}
仅存在Dummy
定义,以便能够在编译时将调用插入到所需的方法中,而在运行时,动态定义的类会在调用该方法之前取代其位置。
但是要小心处理。如前所述,自定义类加载器是最安全的解决方案。通常,您应该通过始终存在的接口创建扩展的编译时引用,并且仅动态地加载实现,可以在运行时将其强制转换为接口,然后通过接口定义的API进行使用。