【问题标题】:Dynamically compile java code which has dependencies on classes loaded by specific classloader动态编译依赖于特定类加载器加载的类的java代码
【发布时间】:2017-04-20 09:16:59
【问题描述】:

我们有能力动态编译java代码。 我至少知道Java-Runtime-CompilerInMemoryJavaCompiler

但似乎他们无法编译依赖于某些类加载器的某些类的类。

是否可以动态编译依赖于仅在特定类加载器中可用的类的 java 代码?比方说:

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 中到底能做什么:

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

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

【问题讨论】:

标签: java groovy compilation jvm classloader


【解决方案1】:

您的类路径上应该有所有依赖项。无论如何,您引用的工具都使用 Java Compiler API。

它不与当前 JVM 内存中的类交互,它只搜索类路径中的依赖关系。

您可以通过CompilerUtils -> com.sun.tools.javac.api.JavacTool -> 进一步了解那里发生的事情。

您可以尝试做的一件事是将您的动态编译的依赖项作为 .class 文件转储到类路径中的适当位置,以便您的编译过程将它们拾取。

【讨论】:

  • 是的,这也是我正在考虑的选项。从 URL 类加载器获取 url 并使用它们动态编译该 java 代码之类的东西。我希望会有另一种解决方案。
【解决方案2】:

没有办法使用ClassLoader 作为引用,除非它能够提供其定义的类的类字节。即,如果您有一个代表顶级类的 Class 实例,则可以使用 classInstance.getResourceAsStream(classInstance.getSimpleName()+".class") 尝试获取类字节。如果您有权访问构成动态类的字节,则可以通过 JavaFileManager 实现将它们提供给 java 编译器。

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

// 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()));
    }
}

当然,这只是为了演示原理。您肯定想为文件对象创建工厂方法并使用Maps 来记住它们等。

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

【讨论】:

    猜你喜欢
    • 2012-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    相关资源
    最近更新 更多