JavaCompiler动态编译java代码

运行时将一段String流编译成一个class文件

自定义compiler

核心代码是JavaCompiler.CompilationTask, 通过调用JavaCompiler.CompilationTask.call方法

编译无误返回true,反之返回false

参数:out - 用于来自编译器的其他输出的 Writer;如果为 null,则使用 System.err

  fileManager - 文件管理器;如果为 null,则使用编译器的标准文件管理器标准文件管理器有两个用途:

    • 自定义编译器如何读写文件的基本构建块

     • 在多个编译任务之间共享

  diagnosticListener - 诊断侦听器;如果为 null,则使用编译器的默认方法报告诊断信息

  options - 编译器选项; null 表示没有选项

  classes - 类名称(用于注释处理), null 表示没有类名称

  compilationUnits - 要编译的编译单元; null 表示没有编译单元

此处我们自定了fileManager,用于接收String类型输入。编译单元是单个的String流

package io.github.kennethfan.compiler;

import javax.tools.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Created by kenneth on 2023/5/28.
 */
public class RuntimeCompiler {

    private JavaCompiler javaCompiler;

    public RuntimeCompiler() {
        this.javaCompiler = ToolProvider.getSystemJavaCompiler();
    }

    public Class<?> compileAndLoad(String fullName, String sourceCode) throws ClassNotFoundException {
        DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
        JavaFileManager javaFileManager = new ClassFileManager(this.javaCompiler.getStandardFileManager(diagnosticCollector, null, null));

        List<JavaFileObject> javaFileObjectList = new ArrayList<>();
        javaFileObjectList.add(new JavaSourceObject(fullName, sourceCode));

        JavaCompiler.CompilationTask task = this.javaCompiler.getTask(null, javaFileManager, diagnosticCollector, null, null, javaFileObjectList);
        if (task.call()) {
            return javaFileManager.getClassLoader(null).loadClass(fullName);
        }

        System.out.println(diagnosticCollector.getDiagnostics().get(0).getLineNumber());
        System.out.println(diagnosticCollector.getDiagnostics().get(0).getColumnNumber());
        System.out.println(diagnosticCollector.getDiagnostics().get(0).getMessage(Locale.ENGLISH));
        System.out.println(diagnosticCollector.getDiagnostics().get(0).getSource());
        System.out.println(diagnosticCollector.getDiagnostics().get(0).getCode());

        return Class.forName(fullName);
    }
}

自定义源文件处理

自定义源文件处理,核心方法是getCharContent,返回源码内容,kind=SOURCE

package io.github.kennethfan.compiler;

import javax.tools.SimpleJavaFileObject;
import java.io.IOException;
import java.net.URI;

/**
 * Created by kenneth on 2023/5/28.
 */
class JavaSourceObject extends SimpleJavaFileObject {
    /**
     * 类名
     */
    private final String className;
    /**
     * 源代码
     */
    private final String sourceCode;

    public JavaSourceObject(String className, String sourceCode) {
        super(URI.create("String:///" + className.replaceAll("\\.", ".") + Kind.SOURCE.extension), Kind.SOURCE);
        this.className = className;
        this.sourceCode = sourceCode;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
        return this.sourceCode;
    }

    public String getClassName() {
        return className;
    }
}

自定义输出文件处理

接下来自定义编译class文件处理,

class JavaClassObject extends SimpleJavaFileObject {

    private final String className;
    private final ByteArrayOutputStream byteArrayOutputStream;

    public JavaClassObject(String className) {
        super(URI.create("String:///" + className.replaceAll("\\.", "/") + Kind.CLASS.extension), Kind.CLASS);
        this.className = className;
        this.byteArrayOutputStream = new ByteArrayOutputStream();
    }

    @Override
    public OutputStream openOutputStream() throws IOException {
        return this.byteArrayOutputStream;
    }

    public String getClassName() {
        return className;
    }

    public byte[] getBytes() {
        return this.byteArrayOutputStream.toByteArray();
    }
}

自定义JavaFileManager

接下来实现JavaFileManager,此处继承ForwardingJavaFileManager

package io.github.kennethfan.compiler;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.security.SecureClassLoader;

/**
 * Created by kenneth on 2023/5/28.
 */
public class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {

    private JavaClassObject javaClassObject;

    public ClassFileManager(JavaFileManager fileManager) {
        super(fileManager);
    }

    @Override
    public ClassLoader getClassLoader(Location location) {
        return new SecureClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                byte[] bytes = javaClassObject.getBytes();
                return super.defineClass(name, bytes, 0, bytes.length);
            }
        };
    }

    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
        this.javaClassObject = new JavaClassObject(className);
        return this.javaClassObject;
    }
}

测试

import io.github.kennethfan.compiler.RuntimeCompiler;
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RuntimeCompilerTest {


    @Test
    public void test_static() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        String classStream = "public class TestClass {\n" +
                "\n" +
                "    public int add(int a, int b) {\n" +
                "        return a + b;\n" +
                "    }\n" +
                "\n" +
                "    public static double pow(double a, double b) {\n" +
                "        return Math.pow(a, b);\n" +
                "    }\n" +
                "}\n";

        Class<?> clazz = new RuntimeCompiler().compileAndLoad("TestClass", classStream);
        Method staticMethod = clazz.getMethod("pow", double.class, double.class);
        double a = 2.0d;
        double b = 3.0d;
        Assert.assertEquals(8.0d, staticMethod.invoke(null, a, b));
    }

    @Test
    public void test_normal() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
        String classStream = "public class TestClass {\n" +
                "\n" +
                "    public int add(int a, int b) {\n" +
                "        return a + b;\n" +
                "    }\n" +
                "\n" +
                "    public static double pow(double a, double b) {\n" +
                "        return Math.pow(a, b);\n" +
                "    }\n" +
                "}\n";

        Class<?> clazz = new RuntimeCompiler().compileAndLoad("TestClass", classStream);
        Method staticMethod = clazz.getMethod("add", int.class, int.class);
        Assert.assertEquals(8, staticMethod.invoke(clazz.newInstance(), 5, 3));
    }
}

Last updated