前言

  • 使用过leetcode或者类似在线编译网站功能的人,或许会比较感兴趣,关于在线编译的实现原理,由于我比较头铁,所以一冲动之下毕业设计的项目选择制作一个类似于在线编译的一个网站。
  • 在决定做这个之前,大概对这方面的东西一窍不通,网上的资料很多也是比较千篇一律,给我这种萌新带来的难度不是一点半点,当然,最终收获还是挺大的,所以想写一点东西,作为梳理,也给以后想学的人做一个参考作用(其实在写的过程中还是踩了一些坑的)。
  • 最终,其实成果挺水的,做出来的成品,就只是实现了一个简陋的Java语言的在线编译功能,这里也想吐槽一下,其实leetcode,支持那么多语言的在线编译真的挺厉害的。

前期准备

首先在运行java程序之前,肯定要想办法把.java的文件使用编译器,编译成.class的字节码文件。
运气好的是,强大的Java已经具备类似的API,就是JavaCompiler类,下面做一点简单介绍:
JavaCompiler是java语言自带的一个接口,大概是一个对Java编译器的一个抽象,通过ToolProvider 类的静态方法获取其实现对象:

public interface JavaCompiler extends Tool, OptionChecker
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

稍微看一下源码

private static final String defaultJavaCompilerName= "com.sun.tools.javac.api.JavacTool";
private static synchronized ToolProvider instance() {if (instance == null)instance = new ToolProvider();return instance;}/*** Gets the Java™ programming language compiler provided* with this platform.* @return the compiler provided with this platform or* {@code null} if no compiler is provided*/public static JavaCompiler getSystemJavaCompiler() {return instance().getSystemTool(JavaCompiler.class, defaultJavaCompilerName);}

可以知道,返回的是一个JavacTool对象,是一个接口实现类

public final class JavacTool implements JavaCompiler {

这个类实现了run方法

public interface Tool {int run(InputStream in, OutputStream out, OutputStream err, String... arguments);
}

各个参数的意思分别是

  • in
    java编译器提供信息
  • out
    用于获取输出信息
  • err
    用于获取错误信息
  • arguments
    编译的文件(路径)

前面三个参数如果,为null则会用默认标准输入输出代替。网上到处都搜的到不做累述。

JavaCompiler V1.0

于是就有了第一种在线编译运行的实现思路,使用文件IO来动态生成.java格式的文件与路径,然后写入代码内容。
最初我便是打算姑且使用这种方式,由于数据封装对象UserDto与Question都具有一个唯一的Id属性,因此 xx.userId.questionId似乎挺适合用来做生成文件的类路径的,类名就可以统一学习leetcode使用Solution ,于是一番努力后写出了我的Compilerv1.0
然而这种方式就给人感觉很low,“java动态编译”听起来还挺屌的,结果一细看,就这?
而且,这样的实现,每次前端给一个请求过来都要进行文件读写操作,如果之前没有建立好相应路径与文件,还得重新新建,于是,当用户和题目多起来了以后那将是一个庞大的文件数量(最大值:用户数X题目数X2),甚至并发量稍微有一点还不知道会出现什么问题。

JavaCompiler V2.0

于是我开始寻找第二种解决方式,这里不得不感谢一个大佬的博客,其实我在线编译的代码很多都是参照这一篇文章的,后期也有自己的研究,但是这篇文章关于JavaCompiler类的介绍,给我开启了新的大门,感兴趣的朋友,链接在下面(应该是原创):
我是链接

我下面要介绍的内容其实和上面的博客可能会有点雷同,就是看了上面博客后有的一点自己的理解

在线编译最理想的情况是:前端表单传给你需要编译的java文件字符串内容,然后将数据直接交给自定义编译器,编译器经过编译后返回Class对象,然后你再进行相应操作。
为了实现这个功能,除了JavaCompiler还需要去了解如下对象:

  • JavaFileObject(大概就是java文件的抽象)
  • JavaFileManager(大概就是Java文件管理操作的封装)

相关内容是从上面博客链接学会的,我自己再做了些改动:

1.需要自定义一个JavaFileObject重写一些方法
2.需要自定义一个JavaFileManager重写一些方法

大致原理就是,由于Java封装的特性,只要类的行为正确,可以关心类的内部细节,所以,获取.java文件内容,最初是从文件中获取,如果我们重写相应方法,意味着我们可以将要编译的String内容,直接返回给相应处理程序,只要调用相应方法,返回的内容正确,其实并不用关心,数据到底是从哪来的。
下面是我的
JavaFileObject实现:

public class JavaFileObjectBean extends SimpleJavaFileObject {/*** Construct a SimpleJavaFileObject of the given kind and with the* given URI.** @param uri  the URI for this file object* @param kind the kind of this file object*/private String javaCode;private ByteArrayOutputStream outputStream;public JavaFileObjectBean(String className, String javaCode) {super(URI.create("string:///"+className.replace(".","/")+Kind.SOURCE.extension), Kind.SOURCE);// System.out.println("string:///" + className.replace(".", "/") + Kind.SOURCE.extension);this.javaCode=javaCode;//this.outputStream=new ByteArrayOutputStream();}protected JavaFileObjectBean(String className, Kind kind)  {super(URI.create("string:///"+className.replace(".","/")+kind.extension), kind);
//        System.out.println("!!");this.outputStream=new ByteArrayOutputStream();}@Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {return this.javaCode;}@Overridepublic OutputStream openOutputStream() throws IOException {return this.outputStream;}public byte[] getBytes(){return this.outputStream.toByteArray();}
}

继承自SimpleJavaFileObject

public class SimpleJavaFileObject implements JavaFileObject

新加了几个属性

private String javaCode;private ByteArrayOutputStream outputStream;
  • javaCode:用来保存需要编译的Java文件内容
  • outputStream:用来保存编译后,Class对象的二进制流

重写了两个构造器方法:

public JavaFileObjectBean(String className, String javaCode) {super(URI.create("string:///"+className.replace(".","/")+Kind.SOURCE.extension), Kind.SOURCE);// System.out.println("string:///" + className.replace(".", "/") + Kind.SOURCE.extension);this.javaCode=javaCode;//this.outputStream=new ByteArrayOutputStream();}protected JavaFileObjectBean(String className, Kind kind)  {super(URI.create("string:///"+className.replace(".","/")+kind.extension), kind);
//        System.out.println("!!");this.outputStream=new ByteArrayOutputStream();}

第一个构造方法:用于自己创建对象时使用,调用父类构造方法的同时初始化属性:javaCode
完成初始化以后,相关对象会调用重写的方法

  @Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {return this.javaCode;}

然后进行编译。
第二个构造方法是给相关API调用,然后会调用重写的方法

@Overridepublic OutputStream openOutputStream() throws IOException {return this.outputStream;}

将编译结果写入提供的IO流

重写好的JavaFileObject类配合自定义JavaFileManager使用

public class JavaFileManagerBean extends ForwardingJavaFileManager {private JavaFileObjectBean javaFileObjectBean;/*** Creates a new instance of ForwardingJavaFileManager.** @param fileManager delegate to this file manager*/protected JavaFileManagerBean(JavaFileManager fileManager) {super(fileManager);//this.javaFileObjectBean= new JavaFileObjectBean();}@Overridepublic ClassLoader getClassLoader(Location location) {return new SecureClassLoader(){@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] bytes = javaFileObjectBean.getBytes();return super.defineClass(name,bytes,0,bytes.length);}};}@Overridepublic JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {this.javaFileObjectBean = new JavaFileObjectBean(className,kind);return this.javaFileObjectBean;}
}

相关API会调用

public JavaFileObject getJavaFileForOutput

此时,内置IO流属性会被初始化,然后写入编译的Class对象的二进制流信息,最后自定义一下类加载器的findClass方法,利用loadClass方法获取编译后得到的结果

cls=manager.getClassLoader(null).loadClass(className);

由于没有实际文件,最后会由下面的代码,寻找到需要加载到的类信息就会调用之前的重写的findClass方法得到Class 对象(defineClass方法用来将二进制流信息还原为Class对象)

if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}

暂时就介绍这么多,剩下的内容以后有时间再整理,如果又没说清楚的地方欢迎指正,觉得还不错的动动小手指点个赞。

实现java语言的在线编译(OnlineJudge)----前言相关推荐

  1. 奥鹏教育微学吧JAVA答案_西交20秋《Java语言》在线作业【标准答案】

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 西交<Java语言>在线作业 试卷总分:100 得分:100 一.单选题 (共 30 道试题,共 60 分) 1.给出下面代码,关于该程序以下 ...

  2. Java语言开发在线音乐推荐网 音乐推荐系统 网易云音乐爬虫 基于用户、物品的协同过滤推荐算法 SSM(Spring+SpringMVC+Mybatis)框架 大数据、人工智能、机器学习项目开发

    Java语言开发在线音乐推荐网 音乐推荐系统 网易云音乐爬虫 基于用户.物品的协同过滤推荐算法 SSM(Spring+SpringMVC+Mybatis)框架 大数据.人工智能.机器学习项目开发Mus ...

  3. 使用Java语言开发在线电影推荐网 电影推荐系统 豆瓣电影爬虫 基于用户、物品的协同过滤推荐算法实现 SSM(Spring+SpringMVC+Mybatis)开发框架 机器学习、人工智能、大数据开发

    使用Java语言开发在线电影推荐网 电影推荐系统 豆瓣电影爬虫 基于用户.物品的协同过滤推荐算法实现 SSM(Spring+SpringMVC+Mybatis)开发框架 机器学习.人工智能.大数据开发 ...

  4. Java语言开发在线美食推荐网 美食推荐系统 基于用户、物品的协同过滤推荐算法实现 SSM(Spring+SpringMVC+Mybatis框架 人工智能、大数据、机器学习项目开发

    Java语言开发在线美食推荐网 美食推荐系统 基于用户.物品的协同过滤推荐算法实现 SSM(Spring+SpringMVC+Mybatis框架 人工智能.大数据.机器学习项目开发FoodRecomm ...

  5. 关于HotSpot VM以及Java语言的动态编译 你可能想知道这些

    目录 1 HotSpot VM的历史 2 HotSpot VM 概述 2.1 编译器 2.2 解释器 2.3 解释型语言 VS 编译型语言 3 动态编译 3.1 什么是动态编译 3.2 HotSpot ...

  6. java语言程序设计在线作业_中石油北京2018秋 《Java语言程序设计》第二次在线作业...

    1   老虎奥鹏 www.aopengzuoye.com 1 对象使用时,下面描述错误的是 A.通过"."运算符调用成员变量和方法 B.通过成员变量的访问权限设定限制自身对这些变量 ...

  7. ubuntu 编译java 语言_ubuntu下编译java程序

    ubuntu下编译java程序 首先需要安装jdk,并配置好相应环境变量 下面以简单的HelloWorld为例 文件名为HelloWorld.java java代码: public class Hel ...

  8. java判断闰年通过多态方法_04748《Java语言程序设计》实验指导书.doc

    04748<Java语言程序设计>实验指导书.doc Java语言程序设计 实验指导书 前言 一.上机实验目的 上机实验的目的是提高学生的分析问题.解决问题的能力和动手能力,通过实践环节理 ...

  9. Java实现PDF在线预览

    Java实现PDF在线预览 前言:之前一直PDF一直是下载后再查看,一直在想如何如何在线预览,现已找到方法,作此笔记,也希望都对其他人有所帮助 代码实现 @Slf4j @Controller @Req ...

最新文章

  1. powershell获取linux文件,powershell如何读取文件名并赋值到变量?
  2. 【邓侃】哈佛大学机器翻译开源项目 OpenNMT的工作原理
  3. 学python的心得体会-Python学习心得
  4. [Win32]一个调试器的实现(六)显示源代码
  5. JAVA深入研究——Method的Invoke方法。
  6. 【STM32】FreeRTOS 任务切换
  7. C++ Primer 5th笔记(chap 18 大型程序工具)使用命名空间成员
  8. COCO数据格式说明
  9. win下 cmd 查询本机开了哪些端口 并查看开启端口的软件
  10. 疯子的算法总结(六) 复杂排序算法 ① 归并排序 merge_sort()
  11. [翻译]Adobe Flash Player 11新特性
  12. Bailian3164 奇偶排序【排序】
  13. ABAP中分页控件的定义
  14. 统一对外的接口,支持requestBody以及表单提交的坑
  15. Fiddler 抓包工具总结
  16. bat批处理注释方法总结
  17. hive系列-hive教程
  18. springboot starter封装永中预览
  19. java之自动化观看视频
  20. 云班课计算机题答案,云班课答案获取

热门文章

  1. oracle判断字段为空时选用别的字段_oracle中使用sql查询时字段为空则赋值默认
  2. matlab静电场有限元分析
  3. Python爬虫 爬取歌曲的评论并写入txt
  4. 玩转NPM,搭建私有仓库-姜威-专题视频课程
  5. leetcode:活字印刷
  6. 使用ArchR分析单细胞ATAC-seq数据(第四章)
  7. 问题 I: 自然数拆分Lunatic版
  8. 【算法竞赛进阶指南】CH5202 自然数拆分Lunatic版 完全背包
  9. c语言变量按作用域范围分两种,第02天C语言(10):变量-作用域
  10. picpick尺子像素大小精度不够准确_光栅尺精度与分辨率有什么关系吗?