• java动态编译 (java在线执行代码后端实现原理)


    需求:要实现一个web网页中输入java代码,然后能知道编译结果以及执行结果
    类似于菜鸟java在线工具的效果:https://c.runoob.com/compile/10

    刚开始从什么概念都没有到最后封装成一个完整的工具类,中间查阅了很多资料才了解其中的概念以及流程,参考文献在文章最后面。

    重点需要了解的概念是:
    JavaFileManage、JavaFileObject
    推荐先看这篇文章:http://blog.onlycatch.com/post/java-Compiler-API

    这里是一个封装的demo代码:

    package compiler.mydemo;
    
    import javax.tools.Diagnostic;
    import javax.tools.DiagnosticCollector;
    import javax.tools.FileObject;
    import javax.tools.ForwardingJavaFileManager;
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileManager;
    import javax.tools.JavaFileObject;
    import javax.tools.SimpleJavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.PrintStream;
    import java.io.UnsupportedEncodingException;
    import java.lang.reflect.InvocationTargetException;
    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;
    
    /**
     * Create by andy on 2018-12-06 21:25
     */
    public class CustomStringJavaCompiler {
        //类全名
        private String fullClassName;
        private String sourceCode;
        //存放编译之后的字节码(key:类全名,value:编译之后输出的字节码)
        private Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>();
        //获取java的编译器
        private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //存放编译过程中输出的信息
        private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
        //执行结果(控制台输出的内容)
        private String runResult;
        //编译耗时(单位ms)
        private long compilerTakeTime;
        //运行耗时(单位ms)
        private long runTakeTime;
    
    
        public CustomStringJavaCompiler(String sourceCode) {
            this.sourceCode = sourceCode;
            this.fullClassName = getFullClassName(sourceCode);
        }
    
        /**
         * 编译字符串源代码,编译失败在 diagnosticsCollector 中获取提示信息
         *
         * @return true:编译成功 false:编译失败
         */
        public boolean compiler() {
            long startTime = System.currentTimeMillis();
            //标准的内容管理器,更换成自己的实现,覆盖部分方法
            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;
            return task.call();
        }
    
        /**
         * 执行main方法,重定向System.out.print
         */
        public void runMainMethod() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, UnsupportedEncodingException {
            PrintStream out = System.out;
            try {
                long startTime = System.currentTimeMillis();
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                PrintStream printStream = new PrintStream(outputStream);
                //PrintStream PrintStream = new PrintStream("/Users/andy/Desktop/tem.sql"); //输出到文件
                System.setOut(printStream);
    
                StringClassLoader scl = new StringClassLoader();
                Class<?> aClass = scl.findClass(fullClassName);
                Method main = aClass.getMethod("main", String[].class);
                Object[] pars = new Object[]{1};
                pars[0] = new String[]{};
                main.invoke(null, pars); //调用main方法
                //设置运行耗时
                runTakeTime = System.currentTimeMillis() - startTime;
                //设置打印输出的内容
                runResult = new String(outputStream.toByteArray(), "utf-8");
            } finally {
                //还原默认打印的对象
                System.setOut(out);
            }
    
        }
    
        /**
         * @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();
        }
    
        /**
         * @return 控制台打印的信息
         */
        public String getRunResult() {
            return runResult;
        }
    
    
        public long getCompilerTakeTime() {
            return compilerTakeTime;
        }
    
        public long getRunTakeTime() {
            return runTakeTime;
        }
    
        /**
         * 获取类的全名称
         *
         * @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);
                }
            }
        }
    }
    
    

    测试代码:

    package compiler.mydemo;
    
    /**
     * Create by andy on 2018-12-06 15:21
     */
    public class Test1 {
    
        public static void main(String[] args) {
            String code = "public class HelloWorld {
    " +
                    "    public static void main(String []args) {
    " +
                    "		for(int i=0; i < 1; i++){
    " +
                    "			       System.out.println("Hello World!");
    " +
                    "		}
    " +
                    "    }
    " +
                    "}";
            CustomStringJavaCompiler compiler = new CustomStringJavaCompiler(code);
            boolean res = compiler.compiler();
            if (res) {
                System.out.println("编译成功");
                System.out.println("compilerTakeTime:" + compiler.getCompilerTakeTime());
                try {
                    compiler.runMainMethod();
                    System.out.println("runTakeTime:" + compiler.getRunTakeTime());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(compiler.getRunResult());
                System.out.println("诊断信息:" + compiler.getCompilerMessage());
            } else {
                System.out.println("编译失败");
                System.out.println(compiler.getCompilerMessage());
            }
    
        }
    
    
    }
    
    

    下一篇介绍了如果处置动态代码的死循环的思路:
    java动态编译 (java在线执行代码后端实现原理)(二)

    下面一些文章对我的理解有很大的帮助:
    http://blog.onlycatch.com/post/java-Compiler-API (这篇文章循环渐进,对理解概念有很大帮助)
    https://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000
    https://www.ibm.com/developerworks/cn/java/j-jcomp/
    http://www.cnblogs.com/liaoyu/p/real-time-compile-and-run-java-code-web-app.html
    http://www.timehaswingss.top/blog/java/dynamic.html

  • 相关阅读:
    多个类定义attr属性重复的问题:Attribute "xxx" has already been defined
    好用的批量改名工具——文件批量改名工具V2.0 绿色版
    得到ImageView中drawable显示的区域的计算方法
    得到view坐标的各种方法
    实现类似于QQ空间相册的点击图片放大,再点后缩小回原来位置
    Material Designer的低版本兼容实现(五)—— ActivityOptionsCompat
    Android 自带图标库 android.R.drawable
    解决 Attempting to destroy the window while drawing!
    解决Using 1.7 requires compiling with Android 4.4 (KitKat); currently using API 4
    Material Designer的低版本兼容实现(四)—— ToolBar
  • 原文地址:https://www.cnblogs.com/andysd/p/10081443.html
Copyright © 2020-2023  润新知