最近在学习lua,然后顺便看了下luaj,可能用的人比较少,网上关于luaj的文章较少,其中在网上找到这个博主的相关文章,很详细,对于要学习luaj的小伙伴可以两篇一起查看,本文在此基础上进行扩展。
本文的luaj版本是:luaj-3.0.1
珠玉在前》》》》Luaj学习笔记(二) - 在Lua中操作Java对象
LuaJ源码中org.luaj.vm2.lib.jse.LuajavaLib,是我们在lua中操作java的主要方法库定义。
luajava有五种方法让我们操作java类:bindClas
、 newInstance
、 new
、 createProxy
、 loadLib
先放上源码,然后我们一步步解析,bindClas
、 newInstance
、 new
使用方案和案例在前一篇文章就已经解释过了,这里不做过多赘述,对于前篇不详细的createProxy
和loadLib
做重点介绍:
public Varargs invoke(Varargs args) {
try {
switch ( opcode ) {
case INIT: {
// LuaValue modname = args.arg1();
LuaValue env = args.arg(2);
LuaTable t = new LuaTable();
bind( t, this.getClass(), NAMES, BINDCLASS );
env.set("luajava", t);
env.get("package").get("loaded").set("luajava", t);
return t;
}
case BINDCLASS: {
final Class clazz = classForName(args.checkjstring(1));
return JavaClass.forClass(clazz);
}
case NEWINSTANCE:
case NEW: {
// get constructor
final LuaValue c = args.checkvalue(1);
final Class clazz = (opcode==NEWINSTANCE? classForName(c.tojstring()): (Class) c.checkuserdata(Class.class));
final Varargs consargs = args.subargs(2);
return JavaClass.forClass(clazz).getConstructor().invoke(consargs);
}
case CREATEPROXY: {
final int niface = args.narg()-1;
if ( niface <= 0 )
throw new LuaError("no interfaces");
final LuaValue lobj = args.checktable(niface+1);
// get the interfaces
final Class[] ifaces = new Class[niface];
for ( int i=0; i<niface; i++ )
ifaces[i] = classForName(args.checkjstring(i+1));
// create the invocation handler
InvocationHandler handler = new ProxyInvocationHandler(lobj);
// create the proxy object
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), ifaces, handler);
// return the proxy
return LuaValue.userdataOf( proxy );
}
case LOADLIB: {
// get constructor
String classname = args.checkjstring(1);
String methodname = args.checkjstring(2);
Class clazz = classForName(classname);
Method method = clazz.getMethod(methodname, new Class[] {});
Object result = method.invoke(clazz, new Object[] {});
if ( result instanceof LuaValue ) {
return (LuaValue) result;
} else {
return NIL;
}
}
default:
throw new LuaError("not yet supported: "+this);
}
} catch (LuaError e) {
throw e;
} catch (InvocationTargetException ite) {
throw new LuaError(ite.getTargetException());
} catch (Exception e) {
throw new LuaError(e);
}
}
bindclass:返回一个JavaClass的类,该类是对我们参数指定的java类的一个包装。bindClass方法返回类实例class,同时可以调用类实例的new方法生成该class的实例object,然后就可以调用实例方法(实例化的调用这一点和学lua时的面向对象的写法很相似)
该方法适合调用java类的
静态方法
和静态属性
,当然也可以用来实例化对象,但newInstance 和 new更符合语义
源码:
case BINDCLASS: {
final Class clazz = classForName(args.checkjstring(1));
return JavaClass.forClass(clazz);
}
JavaClass:
static final LuaValue NEW = valueOf("new");
static JavaClass forClass(Class var0) {
JavaClass var1 = (JavaClass)classes.get(var0);
if (var1 == null) {
classes.put(var0, var1 = new JavaClass(var0));
}
return var1;
}
public LuaValue getConstructor() {
return this.getMethod(NEW);
}
测试java代码:
public class TestClass {
public String s1;
protected String s2;
private String s3;
public static String s4;
public String method001(){ return "method001"; }
protected void method002(){ }
private void method003(){ }
public static String method004(){ return "method004"; }
}
lua脚本:
local className = "com.test.luaj.TestClass"
local classx = luajava.bindClass(className)
print("静态方法调用:",classx:method004())
print("静态属性值:",classx.s4)
local obj = classx:new()
print("实例方法调用:",obj:method001())
print("实例属性值:",obj.s1)
输出结果:
静态方法调用: method004
静态属性值: s4
实例方法调用: method001
实例方法调用: s1
其中,我们在lua脚本中用类实例调用:new()
方法时,实际是invoke JavaClass的new方法,而这个new方法通过getMethod方法拿到的是他的构造器方法(在下一个方法源码中)
通过源码可以看到 newInstance 和 new方法的大部分逻辑是一致的
入参是有区别的:
然后代码对我们在lua中传入的参数,截取后边的一段用作构造器参数,然后调用我们在上一个方法看到JavaClass.forClass包装Class实例,然后获取该类的构造器,然后invoke调用构造器方法,生成对象。
源码:
case NEWINSTANCE:
case NEW: {
// get constructor
final LuaValue c = args.checkvalue(1);
final Class clazz = (opcode==NEWINSTANCE? classForName(c.tojstring()): (Class) c.checkuserdata(Class.class));
final Varargs consargs = args.subargs(2);
return JavaClass.forClass(clazz).getConstructor().invoke(consargs);
}
JavaClass:
public LuaValue getConstructor() {
return getMethod(NEW);
}
LuaValue getMethod(LuaValue key) {
···
Map map = new HashMap();
Constructor[] c = ((Class)m_instance).getConstructors();
List list = new ArrayList();
for ( int i=0; i<c.length; i++ )
if ( Modifier.isPublic(c[i].getModifiers()) )
list.add( JavaConstructor.forConstructor(c[i]) );
switch ( list.size() ) {
case 0: break;
case 1: map.put(NEW, list.get(0)); break;
default: map.put(NEW, JavaConstructor.forConstructors( (JavaConstructor[])list.toArray(new JavaConstructor[list.size()]) ) ); break;
}
···
}
测试代码:
local className = "com.test.luaj.TestClass"
local classx = luajava.bindClass(className)
local obj = luajava.new(classx)
print("实例方法调用:",obj:method001())
print("实例属性值:",obj.s1)
print("-----------")
local obj2 = luajava.newInstance(className)
print("newInstance>实例方法调用:",obj2:method001())
print("newInstance>实例属性值:",obj2.s1)
输出结果:
new>实例方法调用: method001
new>实例属性值: s1
-----------
newInstance>实例方法调用: method001
newInstance>实例属性值: s1
createProxy可以像JDKProxy那样的在lua中创建对一个对象的一个或多个方法的代理
事实上,createProxy使用的代理就是JDKProxy,只不过在用法上和我们通常的用法有些许变动,在最终的方法的invoke时,并不是通常的使用JAVA反射中的的Method的invoke,而是Luaj自己LuaValue的invoke,也就是说通过调用在lua脚本中定义的那个扩展方法,去实现代理,实际上的代理逻辑都在lua中定义(下面lua脚本可以看出来)
还有很重要的一点,在lua中通过createproxy生成出来的代理对象,是不能在lua脚本中去直接调用代理对象的方法的,?????只能通过将该代理对象当做参数通过调用类方法或者对象方法之后在java中去触发代理对象的方法。(看下边的测试lua源码)
源码:
case CREATEPROXY: {
final int niface = args.narg()-1;
if ( niface <= 0 )
throw new LuaError("no interfaces");
final LuaValue lobj = args.checktable(niface+1);
// get the interfaces
final Class[] ifaces = new Class[niface];
for ( int i=0; i<niface; i++ )
ifaces[i] = classForName(args.checkjstring(i+1));
// create the invocation handler
InvocationHandler handler = new ProxyInvocationHandler(lobj);
// create the proxy object
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), ifaces, handler);
// return the proxy
return LuaValue.userdataOf( proxy );
}
测试java代码:
public interface Car {
void running(String carName);
void blow(Integer num);
}
-------------------------------------------------
public class Taxi implements Car {
@Override
public void running(String carName) {
System.out.println("The taxi["+carName+"] is running.");
}
@Override
public void blow(Integer num) {
for (int i = 0; i < num; i++) {
System.out.print("didi\n");
}
System.out.print("");
}
public static void triggerProxy(Car car){
String carName = UUID.randomUUID().toString();
car.running(carName);
System.out.println("--------");
car.blow((int)Math.random()*10+1);
}
}
-----------------------------------------------------
public class LuaJCreateProxyTest {
public static void main(String[] args) throws ScriptException {
createProxy002();
}
static void createProxy002() throws ScriptException {
Globals globals = JsePlatform.standardGlobals();
globals.loadfile("res/lua/createProxyTest02.lua").call();
}
}
lua脚本:
interfaceName = "com.test.luaj.Car"
className = "com.test.luaj.Taxi"
taxi = luajava.newInstance(className)
-- InvocationHandler
local exit_cb = {
running = function (carname)
print("这是对Car的代理逻辑----前")
taxi:running(carname)
print("这是对Car的代理逻辑----后")
end,
blow = function(num)
print("Car--blow----前")
taxi:blow(num)
print("Car--blow----后")
end
}
proxyObj = luajava.createProxy(interfaceName ,exit_cb)
-- proxyObj.running() -- 会报错,不能这样使用
--必须通过传参给java方法,在java代码中去调用代理对象的方法
luajava.bindClass(className):triggerProxy(proxyObj)
输出结果:
这是对Car的代理逻辑----前
The taxi[74f253d1-c0d8-47ea-9799-01401174abe4] is running.
这是对Car的代理逻辑----后
--------
Car--blow----前
didi
Car--blow----后
关于loadLib
这个方法,前边文章可能由于历史更新的原因,在luaj-3.0.1版本中代码是不对的
loadLib通过源码查看我,我们其实可以看到,他只用两个参数,第一个是类路径,第二个是静态方法名,这个方法要么返回一个LuaValue类型的结果,要么不返回或者其他返回类型都会被loadLib转为nil
。
源码:
case LOADLIB: {
// get constructor
String classname = args.checkjstring(1);
String methodname = args.checkjstring(2);
Class clazz = classForName(classname);
Method method = clazz.getMethod(methodname, new Class[] {});
Object result = method.invoke(clazz, new Object[] {});
if ( result instanceof LuaValue ) {
return (LuaValue) result;
} else {
return NIL;
}
}
测试java源码:
public static LuaInteger xxx() throws ScriptException {
System.out.println("xxx执行啦!\t"+(int)Math.pow(14,2));
return LuaValue.valueOf((int)Math.pow(14,2));
}
static void test007() throws ScriptException {
Globals globals = JsePlatform.standardGlobals();
globals.loadfile("res/lua/loadLibTest.lua").call();
}
lua脚本
local className = "com.yangsong.luaj.LuaJTest"
local method = 'xxx'
local _, result = luajava.loadLib(className, method)
print(_,result)
输出结果:
xxx执行啦!
196 nil
bindClass
适合做类的静态方法和静态属性的取值的操作,也是使用new
方法的前置操作。
newInstance
和new
适合用作java对象实例化之后对实例对象的操作和取值
createProxy
适合用作JDKProxy的替代用法,需要注意的是在被代理对象和代理对象都在脚本中生成,且代理对象不能直接在lua中去调用代理方法执行,需要以传参的形式给到java方法调用java方法触发。
loadLib
用于类的无参静态方法的调用,如果需要返回值,则需要定义方法返回类型为LuaValue