vue-codemirror + Java Compiler实现Java Web IDE
背景
最近同事告诉我一个很有趣的需求:让用户(应用场景中,一般为其他开发者)自己填入Java代码片段,代码片段的内容为已经规定好的模板类的继承类,实现模板类定义的方法。我们的项目要实现动态编译代码片段,存储代码片段和用户操作记录的映射关系,并能够在业务中载入代码片段执行。
这有点像我们提供一个模板模式的架构,只不过模板类的实现类由外部接口填入代码片段动态实现。相较让其他开发者直接参与项目开发,无疑:
- 降低了侵入风险
- 向其他开发者隐藏了大部分实现
- 降低操作难度和开发门槛
- 便于管理
……
这相当于要实现一个简单的在线Java开发环境,提供基础的代码填写、编译和保存的功能。
效果演示
基于vue-codemirror和Java Compiler的动态编译,实现了上述需求,目前完成的Web端IDE主要功能点包括:
- 页面展示Java代码块(代码高亮,有行号、可自动补全括号等)
- 从服务端获取模板类代码,并提供示例
- 实时动态编译并获取编译结果(通过/失败 todo:返回编译错误信息)
- 将输入字符串加载成Java Class
以及小的功能点:自动缩进、补全括号、切换主题、联动填写类名等等。
下面给出涉及到的技术和实现方法。
CodeMirror
CodeMirror是一个JS库,可以支持实现有丰富的附加功能和多种语言支持。我们项目的前端使用Vue框架,可以很方便地集成并使用CodeMirror提供的插件,实现我们的在线IDE多种特性。
参考:CodeMirror官网
引入
安装依赖: "vue-codemirror": "^4.0.6"
在src目录下的main.js中引入:
import VueCodeMirror from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
Vue.use(VueCodeMirror)
使用
新建组件JavaIDE.vue
<template>
<codemirror ref="codeMirrorEditor" :value="code" :options="cmOptions" @changes="onChange">
</codemirror>
</template>
<script>
import codemirror from "codemirror/lib/codemirror";
require("codemirror/mode/clike/clike.js");
require("codemirror/addon/edit/closebrackets.js");
components: {
codemirror;
}
export default{
data(){
return{
code: "",
cmOptions:{
mode: "text/x-java", //Java语言
theme: "darcula", // 默认主题
autofocus: true,
lineNumbers: true, //显示行号
smartIndent: true, // 自动缩进
autoCloseBrackets: true// 自动补全括号
}
}
}
</script>
组件化地使用它,我们可以方便地操作它绑定的值(code)和其他附加选项(cmOption)。
在组件创建时为code赋值,即可实现加载模板代码。
根据官网,我们可以直接使用CodeMirror的默认构造函数,也可以提供一个
textarea DOM元素作为构造CodeMirror对象的参数。
可以使用readOnly参数将代码块设置为只读。
联动填写类名功能
希望实现:在上面顶栏中填写类名,在代码中联动填写。
实现方式: 使用正则匹配替换代码片段,再进行替换
使用相同的方法,也可以实现动态补全类名等功能
参考更多JavaScript的正则表达式
为输入框加上监听函数@input="changeClassName"
changeClassName(className) {
var reg = new RegExp(/public class .*? extends ActionParamBuilder/);
this.code = this.code.replace(reg,
"public class " + className + " extends ActionParamBuilder"
);
}
切换主题
引入主题css样式文件
import "codemirror/theme/eclipse.css";
import "codemirror/theme/darcula.css";
import "codemirror/theme/blackboard.css";
使用String数组定义支持的主题,并使用 Element-UI提供的Select组件支持主题切换:
<el-select v-model="cmOptions.theme" placeholder="切换主题" @change="changeTheme">
<span slot="prefix">
<el-tooltip content="更换主题">
<a-icon type="skin" style="fontSize:16px;line-height=50px;"/>
</el-tooltip>
</span>
<el-option v-for="(item,index) in supportThemes" :key="index" :label="item" :value="item">
</el-option>
</el-select>
- 使用
slot实现在选择器中嵌入图标,并支持tooltip功能,使工具栏更加紧凑。slot意为插槽,是封装好的组件预留的可以自定义的空间,我们可以使用slot = ""把DOM元素置入到组件内部,非常灵活。
样式覆写
使用!important关键字覆盖原有CodeMirror样式。注意,将该样式放在全局而不是局部scoped样式表中。
.CodeMirror {
height: 500px !important;
}
JavaCompiler
不用将传入的代码保存成.java文件写入磁盘,直接就可以使用JavaCompiler工具对字符串进行编译。
为了实现实时动态编译功能,我搜索了关于如何将字符串编译成class的方法,还看了一些动态代理的实现思路。后来看到这一篇:Java运行时动态生成class的方法,发现这就是我想要的!
使用Java SDK(since 1.6)提供的JavaCompiler工具。该工具提供编译方法:
CompilationTask getTask(Writer out,
JavaFileManager fileManager,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
Iterable<String> options,
Iterable<String> classes,
Iterable<? extends JavaFileObject> compilationUnits);
JavaFileManager
自定义MemoryJavaFileManager,继承ForwardingJavaFileManager<JavaFileManager>,实现从内存字符串中读取JavaFileObject
重点是下面这个方法:
JavaFileObject makeStringSource(String name, String code) {
return new MemoryInputJavaFileObject(name, code);
}
static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
final String code;
MemoryInputJavaFileObject(String name, String code) {
super(URI.create("string:///" + name), Kind.SOURCE);
this.code = code;
}
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
options,可选参数列表,可以增加外部Jar包依赖
因为我们所需要编译的代码里依赖的类来源于外部的Jar包,所以需要将这些Jar包使用option将这些依赖加进去。这一步踩了坑,因为之前没用过,不知道怎么写……最后终于找到了正确的写法:
List<String> optionList =Arrays.asList("-extdirs",extLib);
extLib是外部jar包的路径(目录地址)。可以使用路径分隔符填入多个路径。DiagnosticListener诊断信息监听
加入诊断信息监听器,我们可以拿到编译错误信息,把这些信息反馈给前端,实现实时编译并报错的功能。
DiagnosticCollector diagnosticCollector = new DiagnosticCollector();JavaFileObject待编译的Java对象,调用自定义类MemoryJavaFileManager的makeStringSource方法。可以传入一组编译单元。
完整方法如下:
public Map<String, byte[]> compile(String fileName, String source,String extLib) throws IOException {
try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);
// 传入诊断监听器 size和传入的javaObject相同
DiagnosticCollector diagnosticCollector = new DiagnosticCollector();
List<String> optionList =Arrays.asList("-extdirs",extLib);
CompilationTask task = compiler.getTask(null, manager,diagnosticCollector, optionList, null, Arrays.asList(javaFileObject));
Boolean result = task.call();
if (result == null || !result.booleanValue()) {
throw new RuntimeException("Compilation failed.");
}
return manager.getClassBytes();
}
}
调用代码:
Map<String, byte[]> results = javaStringCompiler.compile(className + ".java", CODE_TO_COMPILE, libDir);
自定义ClassLoader
参考《Java编程的逻辑》中24.5中内容,我们可以使用自定义的
ClassLoader来加载用户代码片段,成为可调用的Class对象。
- 继承
URLClassLoader - 重写
findClass方法
class MemoryClassLoader extends URLClassLoader {
// class name to class bytes:
Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
public MemoryClassLoader(Map<String, byte[]> classBytes) {
super(new URL[0], MemoryClassLoader.class.getClassLoader());
this.classBytes.putAll(classBytes);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
if (buf == null) {
return super.findClass(name);
}
classBytes.remove(name);
return defineClass(name, buf, 0, buf.length);
}
}
自定义类加载器有如下好处:
- 可以自定义读取class文件字节码方法和形式,如:从内存中、指定jar包中,或从数据库/网络读取等
- 实现隔离,可以实现使用同一个类的不同版本
- 实现热部署,动态更新类的内容
总结
本篇中主要涉及知识点:
vue-codemirror集成和使用JavaCompiler的使用JavaScript正则和Vue中的插槽(slot)- 自定义
ClassLoader实现动态加载
vue-codemirror + Java Compiler实现Java Web IDE的更多相关文章
- Architecture of a Java Compiler
Architectural Overview A modern optimizing compiler can be logically divided into four parts: Th ...
- Intellij编译时报“java: System Java Compiler was not found in classpath”
问题如下: http://stackoverflow.com/questions/19889145/setting-up-intellij-12-idea-with-java-1-7-and-reso ...
- Java compiler level does not match the version of the installed Java project facet.问题
从同事那里拷贝过来的web项目,导入到eclipse中,出现Java compiler level does not match the version of the installed Java p ...
- 解决Java compiler level does not match the version of the installed Java project facet.问题
其实之前遇到过Java compiler level does not match the version of the installed Java project facet.这个问题,因为当时没 ...
- Java项目转换成Web项目
阐述:有时候我们在Eclipse中导入一个web项目,发现导入到项目中后变成一个Java项目,这让人很蛋疼.本篇主要讲述怎样将这个本该为web项目的Java项目变身回去,以及一些在导入过程中遇到的一些 ...
- .net基础学java系列(二)IDE 之 插件
上一篇文章.net基础学java系列(二)IDE "扎实的基础"+"宽广的视野",基本可以帮我们摆脱码畜.码奴.码农的命运! IT领袖:IT大哥:IT精英:IT ...
- .net基础学java系列(二)IDE
上一篇文章.net基础学java系列(一)视野 废话: "视野"这篇文章,管理员说它比较空洞!也许初学者看不懂表格中的大部分内容!多年的neter估计也有很多不知道的! 有.net ...
- Java基础14:离开IDE,使用java和javac构建项目
更多内容请关注微信公众号[Java技术江湖] 这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux ...
- eclipse 中springboot2.0整合jsp 出现No Java compiler available for configuration options compilerClassName
今天使用eclipse创建springboot整合jsp出现一个问题,在idea中并没有遇到这个问题.最后发现是需要在eclipse中添加一个eclipse依赖,依赖如下: <dependenc ...
随机推荐
- 用libevent写的海康摄像头rtsp客户端
之前一直使用live555作为RTSP的客户端,但其框架臃肿,虽然支持各种格式,但实际中并没有这些需求,关键是其注重于格式的解析,却不注重网络IO,单线程下性能也不高,重新用libevent编写rts ...
- Radware:上周五美国大规模DDoS攻击是如何发生的
10月21日上午,Dyn遭受到拒绝服务(DoS)攻击,造成了托管DNS网络的中断.成千上万的网站因此变得不可访问,其中包括Amazon EC2.当天晚些时候,当攻击者发起第二轮针对Dyn DNS系统的 ...
- 阿里云ECS安装JAVA+MYSQL+NGINX
2019独角兽企业重金招聘Python工程师标准>>> 1.准备工作 查看linux版本: linux版本为CentOS 7.4 查看系统信息: 系统为64位 确保服务器系统处于最新 ...
- 数学--数论--中国剩余定理 拓展 HDU 1788
再次进行中国余数定理 问题描述 我知道部分同学最近在看中国剩余定理,就这个定理本身,还是比较简单的: 假设m1,m2,-,mk两两互素,则下面同余方程组: x≡a1(mod m1) x≡ a2(mod ...
- CF1335E Three Blocks Palindrome
就是我这个菜鸡,赛时写出了 E2 的做法,但是算错复杂度,导致以为自己的做法只能AC E1,就没交到 E2 上,然后赛后秒A..... 题意 定义一种字串为形如:\([\underbrace{a, a ...
- 一元三次方程 double输出 -0.00
求一个 a*x*x*x+b*x*x+c*x+d 的解 题目很简单,但是我输出了-0.00,然后就一直卡着,这个问题以后要注意. 让0.00 编程-0.00的方法有很多. 第一种就是直接特判 if(fa ...
- 你应该知道的Vue高级特性
本文使用的Vue版本:2.6.10 Vue为我们提供了很多高级特性,学习和掌握它们有助于提高你的代码水平. 一.watch进阶 从我们刚开始学习Vue的时候,对于侦听属性,都是简单地如下面一般使用: ...
- spring学习笔记(九)事务学习(上)
前述 这段时间在工作中碰到一个事务相关的问题.先说下这个问题的场景,我们是一个商城项目,正在开发优惠券模块,现在有一个需求是需要批量领取优惠券,而且在领券时,其中一张领取失败不能影响其他符合要求的 ...
- 王颖奇 20171010129《面向对象程序设计(java)》第十七周学习总结
实验十七 线程同步控制 实验时间 2018-12-10 学习总结: 1.Java通过多线程的并发运行提高系统资源利用 率,改善系统性能. 2.假设有两个或两个以上的线程共享 某个对象,每个线程都调用 ...
- 【FPGA篇章七】FPGA系统任务:详述常用的一些系统函数以及使用方法
欢迎大家关注我的微信公众账号,支持程序媛写出更多优秀的文章 系统任务和系统函数是Verilog标准的一部分,都以字符"$"为开头.系统任务可划分为六类,下面分别给出一些常用任务的用 ...