Class.forName(名称,实例化,classLoader)不会将类添加到classpath [英] Class.forName(name, instantiation, classLoader) doesn't add class to classpath

查看:125
本文介绍了Class.forName(名称,实例化,classLoader)不会将类添加到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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆