【发布时间】:2019-06-06 02:31:28
【问题描述】:
我在运行时生成.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.这种方法(首先编译.java文件,使用编译器API,然后在第二步将其添加到类加载器)是否正确?能够立即使用代码中的类。
2. AFAIK,有两种方法可以在运行时将类动态加载到类路径中:一种是使用这样的自定义 ClassLoader:(我在编译时出错,因为它抱怨 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 });
另一种方法是使用Class.forName(name, instantiation, classLoader) 将一个类添加到类路径中(同时也提供类引用)。如上所述,由于出现编译器错误(Java 11),我无法应用第一种方法。
关于第二种方法,如果我们这样调用默认类加载器,Class.forName(name, instantiation, classLoader) 会将新类附加到classpath 吗? :
Class.forName("com.foo.Bar",true, ClassLoader.getSystemClassLoader());
// or:
Class.forName("com.foo.Bar",true, ApiHandler.class.getClassLoader());
这对我不起作用。上述 classLoader 参数的哪个变体
是正确的,为什么这些不起作用?是否必须创建自定义类加载器并将其传递给Class.forName()?
3. 我在 Eclipse 项目的 src 文件夹中的 com.foo 包中制作 .java 文件。他们编译的.class 文件也在同一个文件夹中生成(使用编译器API)。当我使用 Eclipse 刷新项目时(右键单击项目 -> 刷新),相关的 .class 文件将在 target/classes 文件夹中生成,此时可以通过代码访问类(例如使用 Class.forName("com.foo.Bar).如果我在target/classes 文件夹中生成.class 文件(通过编译器API),是否可以识别这些类而无需将它们引入类路径?
更新:
我能够在我的代码中使用已编译的类,方法是将受人尊敬的.class 文件保存在项目的target/classes 文件夹中,如上面第三个问题中提到的)。 (通过在编译器的getTask() 方法中添加-d 选项:
Iterable<String> options = Arrays.asList( new String[] { "-d", System.getProperty("user.dir") + "/target/classes/"} );
.
.
.
CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
这样,似乎甚至不需要使用 classLoader 将类添加到类路径中;因为可以使用简单的Class.forName() 访问该类。 你如何解释这个?
Class<?> cls1 = Class.forName("com.foo.Bar");
当然还有通过ClassLoader的方式:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> cls = classLoader.loadClass("com.foo.Bar");
【问题讨论】:
-
URIClassLoader?
-
@dan1st 如果你的意思是代码 sn-p 来制作自定义 classLoader,我从 here 得到代码,这里也是利用
URLClassLoader制作自定义 ClassLoader 的表示:stackoverflow.com/a/1011126/190929 -
我认为它可以帮助你。
-
@dan1st 谢谢,我不知道如何以不同于我之前评论中上述链接的方式使用它。
-
您在“one is using a custom ClassLoader”之后的代码 sn-p 未使用自定义类加载器,而是试图侵入系统类加载器。 @dan1st 建议使用自定义类加载器,创建一个新的
URLClassLoader并在其上调用loadClass或使用Class.forName("com.foo.Bar", irrelevant, theNewlyCreatedLoader)。当您告诉编译器写入类路径中的目录时,该类可能变得可用,但前提是 a) 之前没有尝试加载它并且 a) 类路径上有一个目录(而不是 jar文件或模块存储)。
标签: java classloader dynamic-class-loaders