最近在作一些奇怪的東西,須要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也有足夠的瞭解了,因而把一些相關的代碼整理以下:html
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的執行中中斷。java
首先容我介紹一下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 = null |