专注Java教育14年 全国咨询/投诉热线:444-1124-454
赢咖4LOGO图
始于2009,口口相传的Java黄埔军校
首页 hot资讯 基于Spring Boot的Java在线编译工具

基于Spring Boot的Java在线编译工具

更新时间:2021-11-18 10:50:03 来源:赢咖4 浏览756次

Java开发工具中,有一种是基于Spring Boot的Java在线编译工具,下面小编来给大家介绍。

项目运行流程

程序运行流程图如下

接下来开始具体分析每一步的实现方法

一个Java程序是怎样运行起来的

想要实现在线运行Java代码的需求,我们首先需要了解Java程序正常的编译和运行流程。

首先源代码文件(.java)经由编译器编译成字节码

例如JDK中的javac命令就是实现字节码生成技术的程序

接下来有Java虚拟机解释并运行字节码文件,运行过程有分为两个步骤

类的加载

应用程序运行后,系统会启动一个虚拟机进程。JVM进程在类的加载阶段首先会通过一个类的全限定类名获取定义此类的二进制字节流,然后将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,并且在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。

类加载的相关的内容比较复杂,生成对应的Class对象后还会进行验证、准备、解析、初始化等一系列步骤才算加载完成,但考虑到篇幅问题这里就不再展开说明了。

类的执行

当类加载完成后JVM就可以找到main方法执行了。

本项目中使用反射来完成这一步骤。

明确了以上步骤后,我们发现有三个问题需要解决:

如何编译提交到服务器的Java代码?

在本地运行Java代码的时候我们可以选用Javac命令编译。对于本项目而言,这种方式需要我们先将源代码写入一个.java文件,再编译得到.class文件。但是这样一来不仅非常耗时,而且还会生成额外的文件,导致服务器环境被污染。因此我们选择使用JDK1.6以后添加的动态编译API来解决这一问题。

如何执行编译之后的代码?

一段程序往往不是编写、运行一次就能达到效果的。同一个类可能需要反复的修改、提交、运行。另外,提交的类也要能访问服务端的其他类库才行,对于这一问题,需要我们自己编写类加载器来实现需求。

如何收集Java代码的执行结果?

我们需要把程序向标准输出(System.out)和标准错误输出(System.err)中打印的信息收集起来返回给客户端。但是标准输出设备是整个虚拟机进程全局共享的资源。如果使用System.setOut()/System.setErr()方法将输出流重定向到自己定义的PrintStream上固然可以收集信息,但在多线程情况下这样会连带其他线程的信息一起收集了,这显然不是我们希望看到的。因此我们选择将程序中的System替换为我们自己写的HackSystem类。

也就是说,我们的重点在于实现编译模块和运行模块。 在理清以上思路后,我们就可以正式开始代码的编写了。

Spring Boot相关

在正式开始编码前还要罗嗦一下,本项目选择使用Spring Boot仅仅是看中了它在开发web应用时的方便、快捷,项目中并不会涉及太多框架方面的知识。

如果对于Spring Boot的自动配置原理感兴趣,可以阅读下笔者写的另一篇文章,记录了笔者对于Spring Boot自动配置原理的一些粗浅认识,欢迎各位大神斧正。

编译模块:compile

使用动态编译的方式可以直接在内存中对一个Java程序进行编译并输出到内存中,提高程序运行效率的同时还不会污染服务器环境,可谓一举两得。具体实现步骤如下。

动态编译

关于动态编译的API全部放在javax.tools包下,本项目中主要涉及到的类和接口如下所示:

编译器:

JavaCompiler

ToolProvider

源代码文件:

JavaFileObject

SimpleJavaFIleObject

文件管理器:

JavaFileManager

StandardJavaFileManager

ForwardingJavaFileManager

收集诊断信息:

DiagnosticListener

DiagnosticCollector

接下来开始具体介绍实现动态编译的步骤

准备编译器对象

只有一种方法:

//获取Java语言编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//开始执行编译,通过传入自己的JavaFileManager为编译器创建存放字节码的JavaFIleObject对象
Boolean result = compiler.getTask(null,javaFileManager,compileCollector,
                null,null, Arrays.asList(sourceJavaFileObject)).call();

关于ToolProvider这里有一个坑,如果使用的是OpenJDK,tools.jar文件是放在%JAVA_HOME%/lib下的,运行起来就会报空指针异常。因为启动java的目录默认是%JAVA_HOME%/jre/bin/java.exe,这个目录的lib目录为%JAVA_HOME%/jre/lib,里面没有tools.jar。因此要么把文件拷到指定的lib下,要么干脆使用Oracle JDK也是一切正常。

可以看到执行编译这个方法要填一大堆参数,这些参数就是我们实现在内存中编译源代码的关键。

API中对于这个方法参数的解释如下

JavaCompiler.CompilationTask getTask(Writer out,
                                     JavaFileManager fileManager,
                                     DiagnosticListener<? super JavaFileObject> diagnosticListener,
                                     Iterable<String> options,
                                     Iterable<String> classes,
                                     Iterable<? extends JavaFileObject> compilationUnits)

out - 用于编译器的附加输出; 如果为null使用的就是使用System.err

fileManager - 文件管理器; 如果null使用编译器的标准文件管理器

diagnosticListener - 诊断信息收集器; 如果为null则使用编译器的默认方法来报告诊断

options - 编译器选项, null表示没有选项

classes - 通过注释处理类的名称, null表示没有类名

compilationUnits - 编译单元, null表示无编译单位

大家如果想了解更多相关知识,不妨来关注一下赢咖4的Java赢咖4在线学习,里面的课程内容丰富,从入门到精通,适合小白学习,希望对大家能够有所帮助。

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>