在上一篇java动态编译 (java在线执行代码后端实现原理(一))文章中实现了 字符串编译成字节码,然后通过反射来运行代码的demo。这一篇文章提供一个如何防止死循环的代码占用cpu的问题。

思路:由于CustomStringJavaCompiler中重定向了System.out的输出位置,肯定不能有多线程并发的情况,否则会照成System.out输出内容错乱,所以我用了 Executors.newFixedThreadPool(1), 通过Future模式来获取结果,我自定义了一个CustomCallable来处理核心逻辑,在call方法中重新new 了一个Thread来编译并执行代码,然后通过join等待N秒之后强制stop掉正在运行的线程。这样就能及时的kill掉动态运行的代码。

CustomStringJavaCompiler 编译核心类

package compiler.mydemo;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern; /**
* Create by andy on 2018-12-06 21:25
*/
public class CustomStringJavaCompiler {
//类全名
private String fullClassName;
private String sourceCode;
//存放编译之后的字节码(key:类全名,value:编译之后输出的字节码)
private Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>();
//获取java的编译器
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//存放编译过程中输出的信息
private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
//执行结果(控制台输出的内容)
private String runResult;
//编译耗时(单位ms)
private long compilerTakeTime;
//运行耗时(单位ms)
private long runTakeTime; public CustomStringJavaCompiler(String sourceCode) {
this.sourceCode = sourceCode;
this.fullClassName = getFullClassName(sourceCode);
} /**
* 编译字符串源代码,编译失败在 diagnosticsCollector 中获取提示信息
*
* @return true:编译成功 false:编译失败
*/
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();
} /**
* 执行main方法,重定向System.out.print
*/
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); //测试kill线程暂时屏蔽 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;
//设置打印输出的内容
runResult = new String(outputStream.toByteArray(), "utf-8");
} finally {
//还原默认打印的对象
System.setOut(out);
} } /**
* @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();
} /**
* @return 控制台打印的信息
*/
public String getRunResult() {
return runResult;
} public long getCompilerTakeTime() {
return compilerTakeTime;
} public long getRunTakeTime() {
return runTakeTime;
} /**
* 获取类的全名称
*
* @param sourceCode 源码
* @return 类的全名称
*/
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;
} /**
* 自定义一个字符串的源码对象
*/
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;
} } /**
* 自定义一个编译之后的字节码对象
*/
private class ByteJavaFileObject extends SimpleJavaFileObject {
//存放编译后的字节码
private ByteArrayOutputStream outPutStream; public ByteJavaFileObject(String className, Kind kind) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), kind);
} //StringJavaFileManage 编译之后的字节码输出会调用该方法(把字节码输出到outputStream)
@Override
public OutputStream openOutputStream() {
outPutStream = new ByteArrayOutputStream();
return outPutStream;
} //在类加载器加载的时候需要用到
public byte[] getCompiledBytes() {
return outPutStream.toByteArray();
}
} /**
* 自定义一个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 StringClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ByteJavaFileObject fileObject = javaFileObjectMap.get(name);
if (fileObject != null) {
byte[] bytes = fileObject.getCompiledBytes();
return defineClass(name, bytes, 0, bytes.length);
}
try {
return ClassLoader.getSystemClassLoader().loadClass(name);
} catch (Exception e) {
return super.findClass(name);
}
}
}
}

CustomCallable 调用编译并运行,设置超时时间

package compiler.mydemo;

import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Callable; /**
* Create by andy on 2018-12-07 13:10
*/
public class CustomCallable implements Callable<RunInfo> {
private String sourceCode; public CustomCallable(String sourceCode) {
this.sourceCode = sourceCode;
} //方案1
//@Override
//public RunInfo call() throws Exception {
// System.out.println("开始执行call" + LocalTime.now());
// RunInfo runInfo = new RunInfo();
// CustomStringJavaCompiler compiler = new CustomStringJavaCompiler(sourceCode);
// if (compiler.compiler()) {
// runInfo.setCompilerSuccess(true);
// try {
// compiler.runMainMethod();
// runInfo.setRunSuccess(true);
// runInfo.setRunTakeTime(compiler.getRunTakeTime());
// runInfo.setRunMessage(compiler.getRunResult()); //获取运行的时候输出内容
// } catch (Exception e) {
// e.printStackTrace();
// runInfo.setRunSuccess(false);
// runInfo.setRunMessage(e.getMessage());
// }
// } else {
// //编译失败
// runInfo.setCompilerSuccess(false);
// }
// runInfo.setCompilerTakeTime(compiler.getCompilerTakeTime());
// runInfo.setCompilerMessage(compiler.getCompilerMessage());
// System.out.println("call over" + LocalTime.now());
// return runInfo;
//} //方案2
@Override
public RunInfo call() throws Exception {
RunInfo runInfo = new RunInfo();
Thread t1 = new Thread(() -> realCall(runInfo));
t1.start();
try {
t1.join(3000); //等待3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//不管有没有正常执行完成,强制停止t1
t1.stop();
return runInfo;
} private void realCall(RunInfo runInfo) {
CustomStringJavaCompiler compiler = new CustomStringJavaCompiler(sourceCode);
if (compiler.compiler()) {
runInfo.setCompilerSuccess(true);
try {
compiler.runMainMethod();
runInfo.setRunSuccess(true);
runInfo.setRunTakeTime(compiler.getRunTakeTime());
runInfo.setRunMessage(compiler.getRunResult()); //获取运行的时候输出内容
} catch (InvocationTargetException e) {
//反射调用异常了,是因为超时的线程被强制stop了
if ("java.lang.ThreadDeath".equalsIgnoreCase(e.getCause().toString())) {
return;
}
} catch (Exception e) {
e.printStackTrace();
runInfo.setRunSuccess(false);
runInfo.setRunMessage(e.getMessage());
}
} else {
//编译失败
runInfo.setCompilerSuccess(false);
}
runInfo.setCompilerTakeTime(compiler.getCompilerTakeTime());
runInfo.setCompilerMessage(compiler.getCompilerMessage());
runInfo.setTimeOut(false); //走到这一步代表没有超时
}
}

RunInfo 动态编译、运行信息的bean

public class RunInfo {
//true:代表超时
private Boolean timeOut; private Long compilerTakeTime;
private String compilerMessage;
private Boolean compilerSuccess; private Long runTakeTime;
private String runMessage;
private Boolean runSuccess; //省略get和set方法
}

CompilerUtil 把一整套流程封装了一个工具类

package compiler.mydemo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; /**
* Create by andy on 2018-12-07 16:32
*/
public class CompilerUtil {
//这里用一个线程是因为防止System.out输出内容错乱
private static ExecutorService pool = Executors.newFixedThreadPool(1); public static RunInfo getRunInfo(String javaSourceCode) {
RunInfo runInfo;
CustomCallable compilerAndRun = new CustomCallable(javaSourceCode);
Future<RunInfo> future = pool.submit(compilerAndRun);
//方案1
try {
runInfo = future.get();
} catch (Exception e) {
e.printStackTrace();
//代码编译或者运行超时
runInfo = new RunInfo();
runInfo.setTimeOut(true);
} //方案2:不可行的原因:future.get超时会有问题,由于线程池只有1个线程,同时提交10个任务, 当前面几个任务执行时间很长,后面调用get就会立马失败,也就是说get的超时时间是从调用get开始算的,并不是线程真正执行时间开始计算的
//try {
// runInfo = future.get(5, TimeUnit.SECONDS);
// return runInfo;
//} catch (InterruptedException e) {
// System.out.println("future在睡着时被打断");
// e.printStackTrace();
//} catch (ExecutionException e) {
// System.out.println("future在尝试取得任务结果时出错");
// e.printStackTrace();
//} catch (TimeoutException e) {
// System.out.println("future时间超时");
// e.printStackTrace();
// future.cancel(true);
//}
//runInfo = new RunInfo();
//runInfo.setTimeOut(true);
return runInfo; }
}

测试类:

package compiler.mydemo;

/**
* Create by andy on 2018-12-10 10:43
*/
public class Test3 { public static void main(String[] args) throws InterruptedException {
String loop = "public class HelloWorld {\n" +
" public static void main(String[] args) {\n" +
" while(true){\n" +
//" System.out.println(\"Hello World!\");\n" +
" }\n" +
" \n" +
" }\n" +
"}"; String sleep_loop = "public class HelloWorld {\n" +
" public static void main(String[] args) {\n" +
" try {\n" +
" Thread.sleep(6000);\n" +
" } catch (InterruptedException e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
" System.out.println(\"Hello World!\");\n" +
" while(true){\n" +
//" System.out.println(\"Hello World!\");\n" +
" }\n" +
" }\n" +
"}"; String ok = "public class HelloWorld {\n" +
" public static void main(String[] args) {\n" +
" System.out.println(\"Hello World!\");\n" +
" }\n" +
"}"; TestRun t = new TestRun(ok, "thread:ok");
t.start(); TestRun t1 = new TestRun(loop, "thread:loop:");
t1.start();
//
TestRun t2 = new TestRun(sleep_loop, "thread:sleep_loop:");
t2.start(); } } class TestRun extends Thread {
String code; TestRun(String code, String name) {
this.code = code;
super.setName(name);
} @Override
public void run() {
System.out.println(CompilerUtil.getRunInfo(code));
}
}

java动态编译 (java在线执行代码后端实现原理)(二)的更多相关文章

  1. java动态编译 (java在线执行代码后端实现原理)

    需求:要实现一个web网页中输入java代码,然后能知道编译结果以及执行结果 类似于菜鸟java在线工具的效果:https://c.runoob.com/compile/10 刚开始从什么概念都没有到 ...

  2. (转载)JAVA动态编译--字节代码的操纵

    在一般的Java应用开发过程中,开发人员使用Java的方式比较简单.打开惯用的IDE,编写Java源代码,再利用IDE提供的功能直接运行Java 程序就可以了.这种开发模式背后的过程是:开发人员编写的 ...

  3. java动态编译笔记

    1 前言 Java的动态编译知识,真真在实际开发中并不是经常遇到.但是学习java动态编译有助于我们从更深一层次去了解java.对掌握jdk的动态代理模式,这样我们在学习其他一些开源框架的时候就能够知 ...

  4. Java动态编译

    程序产生过程 下图展示了从源代码到可运行程序的过程,正常情况下先编译(明文源码到字节码),后执行(JVM加载字节码,获得类模板,实例化,方法使用).本文来探索下当程序已经开始执行,但在.class甚至 ...

  5. java动态编译类文件并加载到内存中

    如果你想在动态编译并加载了class后,能够用hibernate的数据访问接口以面向对象的方式来操作该class类,请参考这篇博文-http://www.cnblogs.com/anai/p/4270 ...

  6. Java动态编译技术原理

    这里介绍Java动态编译技术原理! 编译,一般来说就是将源代码转换成机器码的过程,比如在C语言中中,将C语言源代码编译成a.out,,但是在Java中的理解可能有点不同,编译指的是将java 源代码转 ...

  7. JAVA动态编译辅助类

    一.场景 平时我们学学用到在JVM运行时,动态编译.java的源代码情况,比如作为灵活的配置文件.这时候就要用到动态编译,参考下列. 二.类内容 1.引入依赖: <!-- https://mvn ...

  8. Java 动态编译--DynamicCompiler

    java 动态编译自己写过程的机会比较少,记录一下: package com.xzlf.dynamicCompile; import java.io.IOException; import java. ...

  9. Java 动态编译组件 & 类动态加载

    1.JDK6 动态编译组件 Java SE 6 之后自身集成了运行时编译的组件:javax.tools,存放在 tools.jar 包里,可以实现 Java 源代码编译,帮助扩展静态应用程序.该包中提 ...

随机推荐

  1. arm-linux内核start_kernel之前启动分析(1)-接过bootloader的衣钵

    前段时间移植uboot细致研究过uboot启动过程,近期耐不住寂寞.想对kernel下手. Uboot启动过程分析博文连接例如以下: http://blog.csdn.net/skyflying201 ...

  2. Sphinx-PHP使用Sphinx搜索技术

    Sphinx继承到PHP程序中, 有两种方式: Sphinx PHP模块: 编译生成PHP扩展模块 Sphinx API类: 直接使用Sphinx提供的类即可 首先我们应该使用Sphinx做以下几件事 ...

  3. bootstrap 页面标题

    页面标题会突出显示,当一个网页中有多个标题且每个标题之间需要添加一定的间距时,页面标题显得特别有用,页面标题会给不是页面标题之间的元素加上横线 加以区分,以突出标题显示. 页面标题类 显示效果图 代码 ...

  4. Mac下忘记了phpAdmin设置的MySQL密码

    事情是这样的,我昨天配置完MySQL和PHP环境后,又配置了phpAdmin. 这本来是件好事,结果我看到phpAdmin提醒我说我的MySQL的Root帐号密码是空密码,于是我修改了下密码. 由于p ...

  5. 中小型研发团队架构实践:分布式协调服务ZooKeeper

    一.ZooKeeper 是什么 Apache ZooKeeper 由 Apache Hadoop 的子项目发展而来,于 2010 年 11 月正式成为了 Apache 的顶级项目. 相关厂商内容 优秀 ...

  6. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

  7. Java分布式 一些概念理解

    转至 java那些事 2017-02-09  有些朋友工作一年了觉得该深入一下子了,所以想深入学习一下以提升自己的专业技能,想问一下如何入门Java分布式应用,学习过程大致是怎么样的,涉及到那些知识, ...

  8. Wpf 抽屉效果

    在android开发中有抽屉效果,就是在页面的边上有一个按钮,可以通过点击或者拖拽这个按钮,让页面显示.Wpf也可以实现相同的效果. 主要是通过一个DoubleAnimation和RectAnimat ...

  9. 点滴积累【JS】---JS小功能(列表页面隔行变色)

    效果: 代码: <head runat="server"> <title></title> <script type="text ...

  10. Atitit.进程管理常用api

    Atitit.进程管理常用api 1 常用api 进程列表 getProcessList 是否存在某个进程判断 isExistProcess 启动进程run Sleep Exit Shutdown 作 ...