动态编译依赖于特定类加载器加载的类的Java代码 [英] Dynamically compile java code which has dependencies on classes loaded by specific classloader

查看:119
本文介绍了动态编译依赖于特定类加载器加载的类的Java代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有能力动态地动态编译Java代码. 我至少知道 Java-Runtime-Compiler

We have an ability to compile java code dynamically on the fly. I know at least Java-Runtime-Compiler and InMemoryJavaCompiler

但是似乎他们不能编译依赖于某些类加载器中某些类的类.

But seems they cannot compile class which depends on some class from certain classloader.

是否可以动态编译仅依赖于特定类加载器中可用类的Java代码?假设:

Is it possible to dynamically compile java code which depends on classes available only in specific classloader? Let's say:

ClassLoader classloader = ... // only this CL can load class 'com.External'
String source = "public class MyClass extends com.External {}";
Class<?> compiled = DesiredDynamicCompiler.compile("MyClass", source, classloader); 
// last argument is like an information to compiler where to search all dependencies


提供更多见解:我想在Java中做GroovyClassLoader在groovy中可以做的事情:


To provide more insight: I would like to do in java what exactly GroovyClassLoader can do in groovy:

GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
Class<?> parsedClass = groovyClassLoader.parseClass("some source");

该代码可以解析仅依赖于指定类加载器中可用类的类.

That code can parse class which depends on classes available only in specified classloader.

推荐答案

除非使用ClassLoader能够提供其已定义类的类字节,否则无法将其用作引用.即,如果有一个表示顶级类的Class实例,则可以使用classInstance.getResourceAsStream(classInstance.getSimpleName()+".class")尝试获取类字节.如果您有权访问构成动态类的字节,则可以通过

There is no way to use a ClassLoader as reference, unless it is capable of providing the class bytes of its defined classes. I.e., if you have a Class instance representing a top-level class, you can use classInstance.getResourceAsStream(classInstance.getSimpleName()+".class") to try to get hands on the class bytes. If you have access to the bytes that make up the dynamic class, you can make them available to the java compiler via a JavaFileManager implementation.

编译器API是标准API的一部分,不需要第三方库.下面的代码通过首先编译一个测试类,然后根据上一步中刚刚创建的类,设置必要的环境来编译第二个类来对此进行演示:

The compiler API is part of the standard API and doesn’t require 3rd party libraries. The following code demonstrates this by compiling a test class first, then setting the necessary environment to compile a second class depending on the class just created in the previous step:

// customize these, if you want, null triggers default behavior
DiagnosticListener<JavaFileObject> diagnosticListener = null;
Locale locale = null;

// the first class, to be present at runtime only
String class1 = "package test;\npublic class Class1 {}";
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, Collections.singleton(
        Files.createTempDirectory("compile-test").toFile()));
JavaCompiler.CompilationTask task = c.getTask(null, fm,
    diagnosticListener, Collections.emptySet(), Collections.emptySet(),
    Collections.singleton(new SimpleJavaFileObject(
        URI.create("string:///Class1.java"), Kind.SOURCE) {
            public CharSequence getCharContent(boolean ignoreEncodingErrors) {
                return class1;
            }
        }));
if(task.call()) {
    FileObject fo = fm.getJavaFileForInput(
            StandardLocation.CLASS_OUTPUT, "test.Class1", Kind.CLASS);
    // these are the class bytes of the first class
    byte[] class1bytes = Files.readAllBytes(Paths.get(fo.toUri()));

    // the actual task: define a class dependent on the first class
    String class2 = "package test;\npublic class Class2 { Class1 variable; }";

    // create a file object representing the dynamic class
    JavaFileObject jo = new SimpleJavaFileObject(
        URI.create("runtime:///test/Class1.class"), Kind.CLASS) {
            @Override public InputStream openInputStream() throws IOException {
                return new ByteArrayInputStream(class1bytes);
            }
        };

    // and a custom file manager knowing how to locate that class
    JavaFileManager myFM = new ForwardingJavaFileManager(fm) {
        @Override
        public JavaFileObject getJavaFileForInput(
                JavaFileManager.Location location, String className, Kind kind)
                throws IOException {
            if(location==StandardLocation.CLASS_PATH&&className.equals("test.Class1")) {
                return jo;
            }
            return super.getJavaFileForInput(location, className, kind);
        }

        @Override
        public boolean hasLocation(JavaFileManager.Location location) {
            return location==StandardLocation.CLASS_PATH || super.hasLocation(location);
        }

        @Override
        public Iterable list(JavaFileManager.Location location,
                String packageName, Set kinds, boolean recurse) throws IOException {
            if(location==StandardLocation.CLASS_PATH
                    && (packageName.equals("test") || recurse&&packageName.isEmpty())) {
                return Collections.singleton(jo);
            }
            return super.list(location, packageName, kinds, recurse);
        }

        @Override
        public String inferBinaryName(
                JavaFileManager.Location location, JavaFileObject file) {
            if(file==jo) return "test.Class1";
            return super.inferBinaryName(location, file);
        }
    };
    // compile the second class using the custom file manager to locate dependencies
    task = c.getTask(null, myFM,
        diagnosticListener, Collections.emptySet(), Collections.emptySet(),
        Collections.singleton(new SimpleJavaFileObject(
            URI.create("string:///Class2.java"), Kind.SOURCE) {
                public CharSequence getCharContent(boolean ignoreEncodingErrors) {
                    return class2;
                }
            }));
    if(task.call()) {
        fo = fm.getJavaFileForInput(
            StandardLocation.CLASS_OUTPUT, "test.Class2", Kind.CLASS);
        // there we have the compiled second class
        byte[] class2bytes = Files.readAllBytes(Paths.get(fo.toUri()));
    }
}

当然,这仅是为了说明原理.您肯定要为文件对象创建工厂方法,并使用Map s来记住它们,等等.

Of course, this is only for demonstrating the principle. You surely want to create factory methods for the file objects and use Maps for remembering them, etc.

也可以用自定义的内存存储空间替换临时目录.但是关键点仍然在于,编译器需要能够访问类字节.它不会使用已加载的运行时类.

It’s also possible to replace the temporary directory with a custom in-memory storage. But the key point remains, that the compiler needs to be able to access the class bytes. It won’t use loaded runtime classes.

这篇关于动态编译依赖于特定类加载器加载的类的Java代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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