专栏首页每天学Java在线运行Java代码

在线运行Java代码

本文将探查 javax.tools 包中,并演示如何使用它们实现Java代码的在线编译 。javax.tools 包以一种通用的方式对这些概念进行了抽象化,使您能够从备用的源代码对象提供源代码,而不要求源代码必须位于文件系统中

前言

在一些网站上有过刷题经历的人,一定会用过在线运行代码的功能,这一篇文章我们就是针对这一功能,来看看如何在线运行我们编辑的Java代码?实际上在JDK1.6的版本中,提供了这样一个包Javax.tools,它可以实现Java 源代码编译,使您能够添加动态功能来扩展静态应用程序,该包是Java 编程语言编译器框架的主要部分,此框架允许框架的客户端查找并运行程序中的编译器。该框架还为结构化访问诊断(DiagnosticListener)提供服务提供者接口(SPI),为重写文件访问提供文件抽象(JavaFileManager 和 JavaFileObject)

官方文档:https://docs.oracle.com/javase/8/docs/api/index.html

在文章前面的部分我们先了解该包下的一些文件,后面的部分我们具体实现在线运行Java代码。

javax.tools 包结构

针对JDK1.6

接口

Diagnostic<S>                用于从工具进行诊断的接口。
DiagnosticListener<S>        用于接收来自工具的诊断的接口
FileObject                   工具的文件抽象。
JavaCompiler                 从程序中调用 Java™ 编程语言编译器的接口。
JavaCompiler.CompilationTask 表示编译任务的 future 的接口
JavaFileManager              在 Java™ 编程语言源和类文件之上进行操作的工具的文件管理器。
JavaFileManager.Location     文件对象位置的接口
JavaFileObject               在 Java™ 编程语言源和类文件上进行操作的工具的文件抽象。
OptionChecker                识别选项的接口。
StandardJavaFileManager      基于 java.io.File 的文件管理器。
Tool                         可以从程序中调用的工具的公共接口

DiagnosticCollector<S>  提供将诊断收集到一个列表中的简单方法
ForwardingFileObject<F extends FileObject> 将调用转发到给定的文件对象。
ForwardingJavaFileManager<M extends JavaFileManager> 将调用转发到给定的文件管理器
ForwardingJavaFileObject<F extends JavaFileObject> 将调用转发到给定的文件对象。
SimpleJavaFileObject 为JavaFileObject 中的大多数方法提供简单实现。
ToolProvider         为查找工具提供者提供方法,例如,编译器的提供者。

枚举

Diagnostic.Kind 诊断的种类,例如,错误或者警告。
JavaFileObject.Kind JavaFileObject 的种类。
StandardLocation 文件对象的标准位置。

文件分析

这一节我们主要来看,上面那些接口具体有什么用,类就不说了,可以去看上面的描述

Diagnostic 通俗的说就是对代码的报错进行诊断,它通常报告源文件中特定位置的问题,但是并非所有诊断都与位置或文件关联。

DiagnosticListener改接口定义了一个方法report,在编译的代码被发现问题时执行,其定义的方法被DiagnosticCollector实现

FileObject 在此上下文中,表示常规文件和其他数据源的抽象。例如,文件对象可用于表示数据库中的常规文件、内存缓存或数据。 如果发生安全异常,此接口中的所有方法都可能抛出SecurityException

JavaFileObject 继承FileObject接口,其内部定义了枚举类Kind,表明JavaFileObject 的种类。

JavaCompiler 为编译器的接口,个人觉得其是包的核心,我们会通过下面的方式,获取编译器

 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

JavaCompiler.CompilationTask 表示编译任务的未来的接口。编译任务尚未启动。要启动该任务,请调用call方法。 在调用调用方法之前,可以配置任务的其他方面,例如,通过调用setProcessors方法。

JavaFileManager内容管理器,在JavaCompiler的getTask方法中,我们需要传入该参数。

JavaFileManager.Location为JavaFileManager内部接口, 用于确定在何处放置或搜索文件对象。

StandardJavaFileManager继承JavaFileManager,是基于java.io.File的文件管理器,获取该类实例的常用方法是使用JavaCompiler的getStandardFileManager:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics =new  DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics, null, null);

Tool可从程序中调用的工具的公共接口。工具通常是命令行程序,如编译器。平台上可用的工具集由供应商定义。其内部仅仅有一个方法isSupportedOption

具体实现

我们先看一下运行效果,我们可以看到编译跟正常运行没有区别(除了速度)

上面实现没有引入其他自定义开发文件类,如果需要import其他自定义开发文件类,可以选择使用反射来实现:

Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("weaver.conn.RecordSet");
 Method[] ms = clazz.getMethods();
Object ob = clazz.newInstance();
System.out.println("===ms" + ms.length);
 Method m = clazz.getMethod("execute", new Class[]{String.class});
System.out.println("====0");
Object o = m.invoke(ob, "select * from hrmResource ");
System.out.println("===x" + o.getClass().getSimpleName());
Method m2 = clazz.getMethod("getCounts");
Integer i = (Integer) m2.invoke(ob);
 System.out.println("===i" + i);

实现步骤

  • 第一步将代码传入到编译器中
  • 第二步编译代码
  • 第三步运行main方法
  • 第四步获取输出
  • 第五步获取运行的编译信息

第一步

定义DynaComplierString类及其构造器

    //类全名
    private String fullClassName;
    //源代码
    private String sourceCode;
    //存放编译之后的字节码(key:类全名,value:编译之后输出的字节码)
    private Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<String, ByteJavaFileObject>();
    //获取java的编译器
    private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    //存放编译过程中输出的信息
    private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<JavaFileObject>();
    //执行结果(控制台输出的内容)
    private String runResult;
    //编译耗时(单位ms)
    private long compilerTakeTime;
    //运行耗时(单位ms)
    private long runTakeTime;
    
    public DynaComplierString(String sourceCode) {
        this.sourceCode = sourceCode;
        this.fullClassName = getFullClassName(sourceCode);
    }

其中fullClassName是获取代码的类名

    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;
    }

第二步

自定义一个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 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;
        }

    }

编译

    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();
    }

第三步

    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;
            //设置打印输出的内容,根据应用服务器来设置GBK还是UTF-8
            runResult = new String(outputStream.toByteArray(), "gbk");
        } finally {
            //还原默认打印的对象
            System.setOut(out);
        }

    }

第四步

 public String getRunResult() {
        return runResult;
    }

第五步

    /**
     * @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();
    }

完整代码请前往小程序进行复制下载

本文分享自微信公众号 - 每天学Java(gh_fddfb9d03324),作者:每天学Java

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-04-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 创建型模式(二)

    关于创建型模式中工厂方法模式,抽象工厂模式在上一篇文章中我们了解了一下,今天我们看一下单例模式,建造者模式,原型模式。

    每天学Java
  • Spring Security:安全访问控制

    “ 在前面的两篇文章中,说了如何使用Spring Boot搭建Security项目以及实现自定义登录认证,今天就拿一个具体的前后端分离项目来看一下安全访问的控制...

    每天学Java
  • Java常用业务代码-IO篇

    Java中File类提供了一系列方法让开发人员对于目录文件进行操作,通常是对目录文件增删:

    每天学Java
  • 每日一题(17)

    看完题目有点迷糊?那看来你并没有掌握好类的初始化顺序 这种题目理清楚执行顺序就好办。执行顺序及解析为题上面程序中的1234

    KEN DO EVERTHING
  • Java源码安全审查

    最近业务需要出一份Java Web应用源码安全审查报告, 对比了市面上数种工具及其分析结果, 基于结果总结了一份规则库. 本文目录结构如下: 

    用户1216491
  • 整理代码,将一些曾经用过的功能整合进一个spring-boot

    由于本人的码云太多太乱了,于是决定一个一个的整合到一个springboot项目里面。

    ydymz
  • springboot实战之常用http客户端整合

    本文主要介绍一下三种http客户端,httpcomponents项目下的httpclient(后边简化描述为httpcomponents-client)、res...

    lyb-geek
  • 七、内存优化(3)使用DMV

    sys.dm_os_memory_clerks返回SQL Server实例中当前处于活动状态的全部内存Clerk的集合。跟踪这个DMV,可以看到内存是如何被SQ...

    py3study
  • Kafka多线程Consumer

    但新版本KafkaConsumer是双线程的,主线程负责:消息获取,rebalance,coordinator,位移提交等等,

    用户6070864
  • 新建网站了!Github标星过万的吴恩达机器学习、深度学习课程笔记,《统计学习方法》代码实现,可以在线阅读了!

    黄博的github是目前最适合初学者入门的机器学习资源之一,注册两年多就收获了目前stat数量38.5k+了,网址:https://github.com/fen...

    黄博的机器学习圈子

扫码关注云+社区

领取腾讯云代金券