java动态编译

1.背景

在某些应用中,我们的代码可能需要动态去执行...

意思就是说,你可以传一段代码去执行

比如:假设我们的业务需要对接上游渠道,而这个上游渠道非常多,随着业务的发展随时都在增加渠道....

这就意味这个,如果不能动态加载代码的话,每上一个渠道,我们就要打包发布一次项目...这样很麻烦,而却也不安全...容易出错

直接代码

2.实现代码

package com.XXX.cashier.hw;

import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
 * 
 * @Date: 2021-06-22 11:48
 * @Description:
 */
public class CustomStringJavaCompiler {
    //类全名
    private String fullClassName;
    private String sourceCode;
    //存放编译之后的字节码(key:类全名,value:编译之后输出的字节码) 也可以存放在磁盘上
    private static Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>();
    //获取java的编译器
    private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    //存放编译过程中输出的信息
    private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
    //编译耗时(单位ms)
    private long compilerTakeTime;

    public CustomStringJavaCompiler(String sourceCode) {
        this.sourceCode = sourceCode;
        this.fullClassName = getFullClassName(sourceCode);
    }

    /**
     * 编译字符串源代码,编译失败在 diagnosticsCollector 中获取提示信息
     *
     * @return true:编译成功 false:编译失败
     */
    public boolean compiler() {
        long startTime = System.currentTimeMillis();
        Object o = javaFileObjectMap.get(fullClassName);
        if (o != null) {
            //设置编译耗时
            compilerTakeTime = System.currentTimeMillis() - startTime;
            System.out.println("使用已有的编译............");
            return true;
        }
        //标准的内容管理器,更换成自己的实现,覆盖部分方法
        StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null);
        JavaFileManager javaFileManager = new StringJavaFileManage(standardFileManager);
        //构造源代码对象
        JavaFileObject javaFileObject = new StringJavaFileObject(fullClassName, sourceCode);
        //获取一个编译任务
        JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticsCollector, null, null, Arrays.asList(javaFileObject));
        //设置编译耗时
        compilerTakeTime = System.currentTimeMillis() - startTime;
        Boolean call = task.call();
        return call;
    }

    /**
     * 执行send 方法
     */
    public String runMainMethod(String json) throws Exception {
        StringClassLoader stringClassLoader = new StringClassLoader();
        Class<?> obj = stringClassLoader.findClass(fullClassName);
        Constructor<?> constructor = obj.getConstructor();
        Object newInstance = constructor.newInstance();
        // 获取方法并执行
        Method method01 = obj.getMethod("send", String.class);
        Object invoke = method01.invoke(newInstance, json);
        System.out.println("执行结束-->" + invoke);
        return invoke.toString();
    }


    /**
     * @return 编译信息(错误 警告)
     */
    public String getCompilerMessage() {
        StringBuilder sb = new StringBuilder();
        List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics();
        for (Diagnostic diagnostic : diagnostics) {
            sb.append(diagnostic.toString()).append("\r\n");
        }
        return sb.toString();
    }


    public long getCompilerTakeTime() {
        return compilerTakeTime;
    }


    /**
     * 获取类的全名称
     *
     * @param sourceCode 源码
     * @return 类的全名称
     */
    public static String getFullClassName(String sourceCode) {
        String className = "";
        Pattern pattern = Pattern.compile("package\\s+\\S+\\s*;");
        Matcher matcher = pattern.matcher(sourceCode);
        if (matcher.find()) {
            className = matcher.group().replaceFirst("package", "").replace(";", "").trim() + ".";
        }

        pattern = Pattern.compile("class\\s+\\S+\\s+\\{");
        matcher = pattern.matcher(sourceCode);
        if (matcher.find()) {
            className += matcher.group().replaceFirst("class", "").replace("{", "").trim();
        }
        return className;
    }

    /**
     * 自定义一个字符串的源码对象
     */
    private class StringJavaFileObject extends SimpleJavaFileObject {
        //等待编译的源码字段
        private String contents;

        //java源代码 => StringJavaFileObject对象 的时候使用
        public StringJavaFileObject(String className, String contents) {
            super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
            this.contents = contents;
        }

        //字符串源码会调用该方法
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return contents;
        }

    }

    /**
     * 自定义一个编译之后的字节码对象
     */
    private class ByteJavaFileObject extends SimpleJavaFileObject {
        //存放编译后的字节码
        private ByteArrayOutputStream outPutStream;

        public ByteJavaFileObject(String className, Kind kind) {
            super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), kind);
        }

        //StringJavaFileManage 编译之后的字节码输出会调用该方法(把字节码输出到outputStream)
        @Override
        public OutputStream openOutputStream() {
            outPutStream = new ByteArrayOutputStream();
            return outPutStream;
        }

        //在类加载器加载的时候需要用到
        public byte[] getCompiledBytes() {
            return outPutStream.toByteArray();
        }
    }

    /**
     * 自定义一个JavaFileManage来控制编译之后字节码的输出位置
     */
    private class StringJavaFileManage extends ForwardingJavaFileManager {
        StringJavaFileManage(JavaFileManager fileManager) {
            super(fileManager);
        }

        //获取输出的文件对象,它表示给定位置处指定类型的指定类。
        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            ByteJavaFileObject javaFileObject = new ByteJavaFileObject(className, kind);
            javaFileObjectMap.put(className, javaFileObject);
            return javaFileObject;
        }
    }

    /**
     * 自定义类加载器, 用来加载动态的字节码
     */
    private class StringClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            ByteJavaFileObject fileObject = javaFileObjectMap.get(name);
            if (fileObject != null) {
                byte[] bytes = fileObject.getCompiledBytes();
                return defineClass(name, bytes, 0, bytes.length);
            }
            try {
                return ClassLoader.getSystemClassLoader().loadClass(name);
            } catch (Exception e) {
                return super.findClass(name);
            }
        }
    }
}

3.测试

  @Test
    public void test() {
        // 这段代码可以是数据库或者磁盘文件读取
        String code = "package com.XXXXX.cashier.order.testCode;\n" +
                "import com.alibaba.fastjson.JSONObject;\n" +
                "import lombok.extern.slf4j.Slf4j;\n" +
                "@Slf4j\n" +
                "public class Demo01 {\n" +
                "    public String send(String json) {\n" +
                "        log.info(\"json=\" + json);\n" +
                "        log.info(\"run method01-------------\");\n" +
                "        JSONObject jsonObject = JSONObject.parseObject(json);\n" +
                "        String orderNO = jsonObject.getString(\"orderNo\");\n" +
                "        JSONObject object = new JSONObject();\n" +
                "        object.put(\"code\", \"100\");\n" +
                "        object.put(\"msg\", \"success\");\n" +
                "        object.put(\"orderNo\", orderNO);\n" +
                "        object.put(\"createTime\", System.currentTimeMillis());\n" +
                "        log.info(\"run end-------------createTime=\"+object.get(\"createTime\"));\n" +
                "        log.info(\"run end-------------orderNO=\"+orderNO);\n" +
                "        return object.toJSONString();\n" +
                "    }\n" +
                "}";
        // 第一次执行
        method(code);
        System.out.println("-------------------");
        // 第二次执行
        method(code);
    }

    private void method(String code) {
        CustomStringJavaCompiler compiler = new CustomStringJavaCompiler(code);
        boolean res = compiler.compiler();
        if (res) {
            try {
                JSONObject object = new JSONObject();
                object.put("productNO", "P001203");
                object.put("orderNo", "001203");
                String result = compiler.runMainMethod(object.toJSONString());
                System.out.println("-----------------result=" + result);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("诊断信息:" + compiler.getCompilerMessage());
        } else {
            System.out.println("编译失败");
            System.out.println(compiler.getCompilerMessage());
        }
    }

执行结果:

 4.注意

如果报错找不到:

java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment

需要引入tools.jar包

完美!

posted @ 2021-06-22 15:57  李东平|一线码农  阅读(820)  评论(0编辑  收藏  举报