前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在线运行Java代码

在线运行Java代码

作者头像
每天学Java
发布2020-06-02 10:09:20
3.7K0
发布2020-06-02 10:09:20
举报
文章被收录于专栏:每天学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

接口

代码语言:javascript
复制

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

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

枚举

代码语言:javascript
复制
Diagnostic.Kind 诊断的种类,例如,错误或者警告。
JavaFileObject.Kind JavaFileObject 的种类。
StandardLocation 文件对象的标准位置。

文件分析

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

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

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

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

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

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

代码语言:javascript
复制
 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

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

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

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

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

代码语言:javascript
复制
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics =new  DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics, null, null);

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

具体实现

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

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

代码语言:javascript
复制
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类及其构造器

代码语言:javascript
复制
    //类全名
    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是获取代码的类名

代码语言:javascript
复制
    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来控制编译之后字节码的输出位置

代码语言:javascript
复制
    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;
        }
    }

自定义一个字符串的源码对象

代码语言:javascript
复制
    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;
        }

    }

编译

代码语言:javascript
复制
    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();
    }

第三步

代码语言:javascript
复制
    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);
        }

    }

第四步

代码语言:javascript
复制
 public String getRunResult() {
        return runResult;
    }

第五步

代码语言:javascript
复制
    /**
     * @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();
    }

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • javax.tools 包结构
  • 文件分析
  • 具体实现
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档