Class.forName(名称,实例化,classLoader)不会将类添加到classpath [英] Class.forName(name, instantiation, classLoader) doesn't add class to classpath
问题描述
我正在运行时生成.java
类文件,需要立即在代码中利用这些类.
因此,我使用Compiler API编译.java
类以制作.class
文件:
I'm generating .java
class files at the runtime and need to utilize those classes inside the code instantly.
So I compile the .java
classes using Compiler API to make .class
files:
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
的方法.我遇到了与此概念相关的一些歧义:
Then I need to get references to those compiled classes using Class.forName()
, But if I just call Class.forName("com.foo.Bar")
it throws ClassNotFoundException
, Assuming it's because the new .class
files aren't added to classpath
I looked for the methods of adding classes to the classpath
at runtime. I encountered some ambiguities related to this concept:
1..这种方法(首先使用编译器API编译.java
文件,然后在第二步将其添加到类加载器中)正确吗?为了能够立即使用代码中的类.
1. Is this approach (of compiling the .java
file first, using compiler API, and add it to class loader at the second step) correct? To be able utilizing the class in the code instantly.
2. AFAIK,有两种方法可以在运行时将类动态加载到类路径中:一种是使用这样的自定义ClassLoader :(我抱怨编译错误,因为它抱怨BuiltinClassLoader
没有addURL
方法):
2. AFAIK, There are 2 methods to dynamically load classes into classpath at runtime: one is using a custom ClassLoader like this: (which I had error to compile as it complained that BuiltinClassLoader
doesn't have addURL
method):
// 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
吗? :
Another method is using Class.forName(name, instantiation, classLoader)
to add a class to the classpath (which gives the class reference at the same too). The first method I wasn't able to apply since I got a compiler error (Java 11) as mentioned above.
Regarding the second method, Would Class.forName(name, instantiation, classLoader)
attach the new classes to the classpath
if we call the default class loader like this? :
Class.forName("com.foo.Bar",true, ClassLoader.getSystemClassLoader());
// or:
Class.forName("com.foo.Bar",true, ApiHandler.class.getClassLoader());
这对我不起作用.上面的classLoader参数的哪个变体
是正确的,为什么这些都不起作用?创建自定义类加载器并将其传递给Class.forName()
是否很必要?
It doesn't work for me. Which variation of the above classLoader arguments
are correct and why do these don't work? Is it mandotary to create a custom classloader and pass it to the Class.forName()
?
3..我正在eclipse项目的src
文件夹中的com.foo
包中的.java
文件.它们的已编译.class
文件也将在同一文件夹中生成(使用编译器API).当我使用eclipse刷新项目时(右键单击项目->刷新),相关的.class
文件将在target/classes
文件夹中生成,这时可以通过代码访问类(例如,使用Class.forName("com.foo.Bar)
).可能是因为如果我在target/classes
文件夹中生成了.class
文件(通过编译器API),这些类可以被识别而无需将它们引入类路径中?
3. I'm making the .java
files inside the com.foo
package in src
folder of the eclipse project. Their compiled .class
files are also generated at the same folder (using compiler API). When I refresh project using eclipse (right-click on the project -> Refresh) the related .class
files would be generated in the target/classes
folder and that's when the classes could be accessed through the code (e.g using Class.forName("com.foo.Bar)
. May it be that if I produce .class
files (by compiler API) in the target/classes
folder, The classes would be recognizable without the need of introducing them to the classpath?
更新:
通过将受尊重的.class
文件保存在项目的target/classes
文件夹中(如上面第三个问题所述),我能够在代码中使用编译的类. (通过在编译器的getTask()
方法中添加-d
选项:
I was able to use the compiled classes in my code, By saving the respected .class
files in the target/classes
folder, mentioned in the 3rd question above) of the project. (By adding -d
option to the compiler's getTask()
method:
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()
访问该类. 您如何解释?
This way, It seems the classes are not even required to be added to the classpath using classLoader; as the class is accessible using a simple Class.forName()
. How Do you explain this?
Class<?> cls1 = Class.forName("com.foo.Bar");
当然也可以通过ClassLoader方式:
And also with through the ClassLoader way, of course:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> cls = classLoader.loadClass("com.foo.Bar");
推荐答案
最安全的解决方案是创建一个新的ClassLoader
实现,并通过新的加载器加载生成的类,如
The safest solution is to create a new ClassLoader
implementation and load the generated classes via the new loader, like shown in this answer.
但是从Java 9开始,如果尚未定义/加载具有该名称的类,则可以在自己的上下文中定义类,即在同一包中定义类.如前所述,这样的类定义甚至可以取代类路径上的定义,只要它尚未被加载即可.因此,不仅随后的Class.forName(String)
调用将被解析为此类的定义,甚至非反射引用也将被解析.
But since Java 9, there is the possibility to define classes within your own context, i.e. within the same package, if no class with that name has been defined/loaded yet. Such a class definition may even supersede an definition on the class path, as said, as long as it has not been loaded yet. So not only subsequent Class.forName(String)
calls will get resolved to this class definition but even non-reflective references.
这可以通过以下程序进行演示.
This can be demonstrated with the following program.
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
定义,以便能够在编译时将调用插入到所需的方法中,而在运行时,动态定义的类将在该方法被调用之前取代其位置.
The Dummy
definition only exists to be able to insert an invocation to the desired method at compile-time, whereas at runtime, the dynamically defined class takes its place, before the method gets invoked.
但是要小心处理.如前所述,自定义类加载器是最安全的解决方案.通常,您应该通过一个始终存在的接口创建对扩展的编译时引用,并且仅动态地加载实现,可以在运行时将其强制转换为接口,然后通过该接口定义的API进行使用.
But handle with care. As said, the custom class loader is the safest solution. Normally, you should create compile-time references to extensions via an interface which is always present and only load implementations dynamically, which can be cast to the interface at runtime and then used through the API defined by the interface.
这篇关于Class.forName(名称,实例化,classLoader)不会将类添加到classpath的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!