在需要灵活配置时Groovy通常时一个不错的选择,但是问题比较多:
第一、二中已经有比较成熟的方案,但是后面的两个问题就不那么好解决了。
执行Groovy脚本的时候我用的是下面的方法:
Class clazz = new GroovyClassLoader().parseClass("your code");
Method method = clazz.getMethod("xxxx");
method.invoke(null);
如果用户编写的脚本是(能用技术解决的东西最好不要用价值观来保证):
while(true){
// do nothing.
}
机器的CPU马上会飚的很高,可能你会想到用Thread.stop()来终止这个罪恶的脚本,但是对于这个脚本:
while(true){
try{/* your code */}
catch(Exception e){ }
}
只能说根本停不下来。因为:
Thread.stop()仅仅是在线程的任意位置抛出ThreadDeath异常。
况且Thread.stop很早就不建议使用了,而是用Thread.interrupt(),但是简单来说中断仅仅是去通知一下目标线程,而不是真的去停掉它。
那么,现在的目标就是如何将中断检查的代码插入到用户编写的脚本中:
public static void checkInterrupted() {
if (Thread.currentThread().isInterrupted()) {
throw new RuntimeException("task is interrupted!");
}
}
将输入的脚本作为字符串来处理,估计会累到吐血!接着自然想到先将源码结构化,那么自然想到在编译的过程中对语法树进行操作即可达到目的:
public static class SafeGroovyClassLoader extends GroovyClassLoader {
protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) {
CompilationUnit compilationUnit = super.createCompilationUnit(config, source);
compilationUnit.addPhaseOperation(new CompilationUnit.SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
ModuleNode ast = source.getAST();
// 自定义visitor来操作
}
}
}
遍历时我们需要处理的节点包括:
在这些块的开始位置插入checkInterrupted方法的调用:
private BlockStatement createCheckBlock(Statement statement) {
if (statement instanceof BlockStatement) {
BlockStatement blockStatement = (BlockStatement) statement;
blockStatement.getStatements().add(0, checkInterruptedStatement);
return blockStatement;
} else if (statement instanceof EmptyStatement) {
BlockStatement blockStatement = new BlockStatement();
blockStatement.getStatements().add(checkInterruptedStatement);
return blockStatement;
} else {
BlockStatement blockStatement = new BlockStatement();
blockStatement.getStatements().add(checkInterruptedStatement);
blockStatement.getStatements().add(statement);
return blockStatement;
}
}
public void visitCatchStatement(CatchStatement catchStatement) {
catchStatement.getCode().visit(this);
catchStatement.setCode(createCheckBlock(catchStatement.getCode()));
}
你想的话可以在checkInterrupted中加入循环次数统计。现在线程可以中断,貌似问题已经解决了,但是谁来中断他?有三种方案:
第一、二种方案每个请求都会创建多余一个线程(不划算),那就来看看第三种方案的实现方式:
// 保存线程开始时间 Map<Thread, Long> threadCache = Maps.newHashMap(); // 执行前将线程插入 threadCache.put(Thread.currentThread(), System.currentTimeMillis()); // 遍历threadCache根据时间判断是否中断
这种方案实现起来要好好考虑一下同步的问题!
在不做限制的情况下,执行上面方法系统直接就停止了,怎么办?办法很简单(当然不是靠价值观):建立黑名单,在遍历语法树时是否调用了黑名单中的方法。
应用这么多,可能大家都需要用到一些脚本的功能,但是能也没有比较把这些都再各个系统里面去实现一把(没啥意义),如果有这么一个打通脚本与应用、数据的平台也不错!