在需要灵活配置时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根据时间判断是否中断
这种方案实现起来要好好考虑一下同步的问题!
在不做限制的情况下,执行上面方法系统直接就停止了,怎么办?办法很简单(当然不是靠价值观):建立黑名单,在遍历语法树时是否调用了黑名单中的方法。
应用这么多,可能大家都需要用到一些脚本的功能,但是能也没有比较把这些都再各个系统里面去实现一把(没啥意义),如果有这么一个打通脚本与应用、数据的平台也不错!