最近在做一些奇怪的东西,需要Java应用能够接受用户提交的脚本并执行,网络部分我选择了NanoHTTPD提供基本的HTTP服务器支持,并在Java能承载的许多脚本语言中选择了很久,比如Rhino,Jython和JRuby之类,但它们都太过庞大,并且很难实现沙盒保护服务器环境。最后我的目光投向了Lua,那个被称为粘合剂的语言。遇到的第一个难题是选择所使用的库,纯Java实现的Lua解释器有很多,什么LuaJ,LuaJava,kahlua,还有不知名的mochalua,jill等等(好多好多),其中许多解释器是纯Java实现的,LuaJava则使用了JNI,考虑再三以后我选择了LuaJ,毕竟是纯Java实现,拿来就能用的。
LuaJ也有对应JME和JSE平台的,JSE版是JME版的超集,还带有LuaJava里的luajava模块,能够直接在.lua中调用Java方法,创建Java实例,是很方便的。
折腾了几天,觉得对LuaJ也有足够的了解了,于是把一些相关的代码整理如下:
1 2 3 4 5 6 7 8 9 |
// 创建一个Lua执行的全局环境。
LuaValue global = JsePlatform.debugGlobals();
// 获得loadstring变量,这个变量存储了一个方法,相当于JavaScript里的eval。
LuaValue loadstring = global.get("loadstring");
// 第一个call()方法是调用loadstring这个方法,其参数中使用了LueValue.valueOf()这个静态方法把Java的数据封装成Lua能够使用的数据,第二个call()方法是执行字符串中的表达式,结果是输出了“Hello world!”。
loadstring.call(LuaValue.valueOf("print('Hello world!')")).call();
// 与之类似的还有loadfile,不过它的作用是接受一个文件路径,读入这个文件的内容,执行时调用call。
global.get("loadfile").call("./test.lua").call();
|
LuaJ直到代码运行结束前都会阻塞线程,这时候开启一个新的线程专门运行即可,但坑爹的是LuaJ运行以后无法中断(即使你中断了它所在的线程),比如你的.lua中有一个while true do end循环,那么你将永远无法中断它,除非退出你的整个Java应用…
怎么样,有没有很坑爹?我谷歌了大半天,发现LuaJ好像是没有官方的解决方案的(同时讨论这类东西的少得可怜!)…我也曾迁移代码到LuaJava上,发现调用了L.close()方法也是不能中断执行,最后终于抓住了一根救命稻草。
这根稻草来自ComputerCraft,一个在MineCraft中模拟计算机的模组,也是使用的LuaJ,但是却能中断一段代码的执行,于是我用jd-gui查看了它的源代码,最终有效实现了LuaJ的执行中中断。
首先容我介绍一下Lua中的一些自带的方法:
debug.sethook()方法能够精确到每一个函数设置钩子回调,这个回调里可以做任何想要做的事情;
coroutine.create()方法能够创建一个协同线程,
coroutine.yield()方法能够暂停这个协同线程(这正是我们想要的),
coroutine.resume()方法用来恢复这个协同线程。
接下来看代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
package net.airtheva;
import java.io.File;
import org.luaj.vm2.LuaThread;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;
import org.luaj.vm2.lib.jse.JsePlatform;
public class LuaWorker {
class _Worker implements Runnable {
@Override
public void run() {
mIsStopping = false;
mIsStopped = false;
// 产生协同线程。
mLuaThread = mCoroutineCreate.call(mLoadString.call(LuaValue.valueOf("while true do print('!') end")));
// 执行协同线程(该线程将被阻塞)。
mCoroutineResume.call(mLuaThread);
}
}
Thread mThread;
LuaValue mGlobal;
LuaValue mLoadString;
LuaValue mDebugSetHook;
LuaValue mNativeCoroutineCreate;
LuaValue mCoroutineCreate;
LuaValue mCoroutineYield;
LuaValue mCoroutineResume;
LuaValue mLuaThread;
boolean mIsStopping = true;
boolean mIsStopped = true;
public LuaWorker() {
mGlobal = JsePlatform.debugGlobals();
mLoadString = mGlobal.get("loadstring");
mDebugSetHook = mGlobal.get("debug").get("sethook");
LuaValue coroutine = mGlobal.get("coroutine");
mNativeCoroutineCreate = coroutine.get("create");
coroutine.set("create", new OneArgFunction() {
@Override
public LuaValue call(LuaValue value) {
Debug.L("Called.");
LuaThread thread = mNativeCoroutineCreate.call(value).checkthread();
mDebugSetHook.invoke(new LuaValue[] {
thread,
new OneArgFunction() {
@Override
public LuaValue call(LuaValue value) {
if(mIsStopping) {
//LuaThread.yield(LuaValue.NIL);
mCoroutineYield.call(); // 暂停本线程,上面那行也能起到一样的效果。
mIsStopped = true;
}
return LuaValue.NIL;
}
},
LuaValue.valueOf("crl"), // 这里ComputerCraft用的是LuaValue.NIL,但我这边好像停不下来…
LuaValue.valueOf(100000) // 这个100000是照着抄的,其实我不知道这是啥意思,等深入使用Lua了应该就会知道了。
});
return thread;
}
});
mCoroutineCreate = coroutine.get("create");
mCoroutineYield = coroutine.get("yield");
mCoroutineResume = coroutine.get("resume");
}
public void Start() {
mThread = new Thread(new _Worker());
mThread.start();
}
public void Stop() {
// 可能回收没做好。
mIsStopping = true;
mThread.interrupt();
mThread |