LuaJava操作Java的方法

最近在学习lua,然后顺便看了下luaj,可能用的人比较少,网上关于luaj的文章较少,其中在网上找到这个博主的相关文章,很详细,对于要学习luaj的小伙伴可以两篇一起查看,本文在此基础上进行扩展。

本文的luaj版本是:luaj-3.0.1

珠玉在前》》》》Luaj学习笔记(二) - 在Lua中操作Java对象

LuaJ源码中org.luaj.vm2.lib.jse.LuajavaLib,是我们在lua中操作java的主要方法库定义。

luajava有五种方法让我们操作java类:bindClasnewInstancenewcreateProxyloadLib

先放上源码,然后我们一步步解析,bindClasnewInstancenew 使用方案和案例在前一篇文章就已经解释过了,这里不做过多赘述,对于前篇不详细的createProxyloadLib做重点介绍:

java 复制代码
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

bindclass :返回一个JavaClass的类,该类是对我们参数指定的java类的一个包装。bindClass方法返回类实例class,同时可以调用类实例的new方法生成该class的实例object,然后就可以调用实例方法(实例化的调用这一点和学lua时的面向对象的写法很相似)

该方法适合调用java类的静态方法静态属性,当然也可以用来实例化对象,但newInstance 和 new更符合语义

源码:

java 复制代码
			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代码:

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脚本:

powershell 复制代码
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

通过源码可以看到 newInstance 和 new方法的大部分逻辑是一致的

入参是有区别的:

  1. newInstance 方法,接受一个字符串的类路径然后转换为Class实例;
  2. new方法接受一个LuaValue对象,从中拿出userdata数据,而这个userdata数据是一个Class实例
    而上一个方法bindClass返回的就是一个包装了Class的LuaValue对象,因此我们可知,在使用new方法的时候,需要先用bindClass方法生成一个Class实例出来,而newInstance就不用了

然后代码对我们在lua中传入的参数,截取后边的一段用作构造器参数,然后调用我们在上一个方法看到JavaClass.forClass包装Class实例,然后获取该类的构造器,然后invoke调用构造器方法,生成对象

源码:

java 复制代码
			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;
			}
···
}

测试代码:

lua 复制代码
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

createProxy可以像JDKProxy那样的在lua中创建对一个对象的一个或多个方法的代理

事实上,createProxy使用的代理就是JDKProxy,只不过在用法上和我们通常的用法有些许变动,在最终的方法的invoke时,并不是通常的使用JAVA反射中的的Method的invoke,而是Luaj自己LuaValue的invoke,也就是说通过调用在lua脚本中定义的那个扩展方法,去实现代理,实际上的代理逻辑都在lua中定义(下面lua脚本可以看出来)

还有很重要的一点,在lua中通过createproxy生成出来的代理对象,是不能在lua脚本中去直接调用代理对象的方法的,?????只能通过将该代理对象当做参数通过调用类方法或者对象方法之后在java中去触发代理对象的方法。(看下边的测试lua源码)

源码:

java 复制代码
			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代码:

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脚本:

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

关于loadLib这个方法,前边文章可能由于历史更新的原因,在luaj-3.0.1版本中代码是不对的

loadLib 通过源码查看我,我们其实可以看到,他只用两个参数,第一个是类路径,第二个是静态方法名,这个方法要么返回一个LuaValue类型的结果,要么不返回或者其他返回类型都会被loadLib转为nil

源码:

java 复制代码
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源码:

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脚本

lua 复制代码
local className = "com.yangsong.luaj.LuaJTest"
local method = 'xxx'
local _, result = luajava.loadLib(className, method)
print(_,result)

输出结果:

复制代码
xxx执行啦!	
196	nil

总结

bindClass适合做类的静态方法和静态属性的取值的操作,也是使用new方法的前置操作。
newInstancenew适合用作java对象实例化之后对实例对象的操作和取值
createProxy适合用作JDKProxy的替代用法,需要注意的是在被代理对象和代理对象都在脚本中生成,且代理对象不能直接在lua中去调用代理方法执行,需要以传参的形式给到java方法调用java方法触发。
loadLib用于类的无参静态方法的调用,如果需要返回值,则需要定义方法返回类型为LuaValue

相关推荐
Chen-Edward5 分钟前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July1 小时前
Hikari连接池
java
微风粼粼2 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad2 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6732 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术2 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
Olrookie3 小时前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi