Class.forName(名称,实例化,classLoader)不会将类添加到类路径

时间:2019-05-21 07:59:47

标签: java classloader dynamic-class-loaders

我正在运行时生成.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");

1 个答案:

答案 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进行使用。