• 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("
    ");
            }
            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;
    " +
                    "import com.alibaba.fastjson.JSONObject;
    " +
                    "import lombok.extern.slf4j.Slf4j;
    " +
                    "@Slf4j
    " +
                    "public class Demo01 {
    " +
                    "    public String send(String json) {
    " +
                    "        log.info("json=" + json);
    " +
                    "        log.info("run method01-------------");
    " +
                    "        JSONObject jsonObject = JSONObject.parseObject(json);
    " +
                    "        String orderNO = jsonObject.getString("orderNo");
    " +
                    "        JSONObject object = new JSONObject();
    " +
                    "        object.put("code", "100");
    " +
                    "        object.put("msg", "success");
    " +
                    "        object.put("orderNo", orderNO);
    " +
                    "        object.put("createTime", System.currentTimeMillis());
    " +
                    "        log.info("run end-------------createTime="+object.get("createTime"));
    " +
                    "        log.info("run end-------------orderNO="+orderNO);
    " +
                    "        return object.toJSONString();
    " +
                    "    }
    " +
                    "}";
            // 第一次执行
            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包

    完美!

  • 相关阅读:
    快速实现进度条
    Cristi Potlog's Chart Control for .NET
    摩托罗拉投资Android外来往戏开辟商Moblyng
    展讯通讯四季度净利润3000万美元同比增长20倍
    罗仕证券上调空中网评级至"买入"
    政协委员郭为:立异体系体例要以运用为先
    软通动力第四季度净盈余390万美元
    华盛顿邮报:iPad 2将扩展抢先职位
    TwitPic借绯闻男星营销 每个单词赚1.6美元
    苹果向2周内新购iPad一代用户退款100美元
  • 原文地址:https://www.cnblogs.com/newAndHui/p/14918955.html
Copyright © 2020-2023  润新知