java动态编译实现

0 概述

本文主要实现java code 动态编译,并使用自定义的ClassLoader加载动态编译生成的字节码。

1 代码

DynamicCompile 类主负责调用JDK API 实现动态编译以及使用ClassLoader加载编译后生成的字节码。

import javax.tools.*;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

public class DynamicCompile {
    private URLClassLoader parentClassLoader;
    private String classpath;
    public DynamicCompile() {
        this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
        this.buildClassPath();// 存在动态安装的问题,需要动态编译类路径
    }

    private void buildClassPath() {
        this.classpath = null;
        StringBuilder sb = new StringBuilder();
        for (URL url : this.parentClassLoader.getURLs()) {
            String p = url.getFile();
            sb.append(p).append(File.pathSeparator); //路径分割符linux为:window系统为;
        }
        this.classpath = sb.toString();
    }
    /**
     * 编译出类
     *
     * @param fullClassName 全路径的类名
     * @param javaCode      java代码
     *
     * @return 目标类
     */
    public Class compileToClass(String fullClassName, String javaCode) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector diagnostics = new DiagnosticCollector<>();
        ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));

        List jfiles = new ArrayList<>();
        jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));

        List options = new ArrayList<>();
        options.add("-encoding");
        options.add("UTF-8");
        options.add("-classpath");
        options.add(this.classpath);

        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
        boolean success = task.call();

        if (success) {
            JavaClassObject jco = fileManager.getJavaClassObject();
            DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
            //加载至内存
            return dynamicClassLoader.loadClass(fullClassName, jco);
        } else {
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                String error = compileError(diagnostic);
                throw new RuntimeException(error);
            }
            throw new RuntimeException("compile error");
        }
    }

    private String compileError(Diagnostic diagnostic) {
        StringBuilder res = new StringBuilder();
        res.append("LineNumber:[").append(diagnostic.getLineNumber()).append("]\n");
        res.append("ColumnNumber:[").append(diagnostic.getColumnNumber()).append("]\n");
        res.append("Message:[").append(diagnostic.getMessage(null)).append("]\n");
        return res.toString();
    }
}

CharSequenceJavaFileObject 用于包装原始的java code 。

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


public class CharSequenceJavaFileObject extends SimpleJavaFileObject {

    private CharSequence code;

    public CharSequenceJavaFileObject(String className, CharSequence code) {
        super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.code = code;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return code;
    }
}

JavaClassObject 存放编译后结果

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


public class JavaClassObject extends SimpleJavaFileObject {

    private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    public JavaClassObject(String name, Kind kind) {
        super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
    }

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

    //编译时候会调用openOutputStream获取输出流,并写数据
    @Override
    public OutputStream openOutputStream() throws IOException {
        return outputStream;
    }
}

文件管理类

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import java.io.IOException;



public class ClassFileManager extends ForwardingJavaFileManager {
    private JavaClassObject jclassObject;

    public ClassFileManager(StandardJavaFileManager standardManager) {
        super(standardManager);
    }

    public JavaClassObject getJavaClassObject() {
        return jclassObject;
    }

    //需要覆盖
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
                                               FileObject sibling) throws IOException {
        jclassObject = new JavaClassObject(className, kind);
        return jclassObject;
    }
}

classLoader 加载编译生成的字节码


public class DynamicClassLoader extends ClassLoader {

    public DynamicClassLoader(ClassLoader parent) {
        super(parent);
    }
    public Class loadClass(String fullName, JavaClassObject jco) {
        byte[] classData = jco.getBytes();
        return this.defineClass(fullName, classData, 0, classData.length);
    }
}

2 测试

public class Main {

    public static void main(String[] args) throws Exception {
        DynamicCompile dynamicCompile = new DynamicCompile();
        String code = "package com.hsc.study.com.hsc.test;\n" +
                "\n" +
                "/**\n" +
                " * Created by yufei on 17/5/13.\n" +
                " */\n" +
                "public class Test {\n" +
                "    @Override\n" +
                "    public String toString() {\n" +
                "        return \"test\"\n" + ";" +
                "    }\n" +
                "}\n";
        Class classz = dynamicCompile.compileToClass("com.hsc.study.Test", code);
        System.out.println(classz.newInstance());
    }
}
public class Test {
    @Override
    public String toString() {
        return "test";
    }
}

输出:test

你可能感兴趣的:(java基础)