业务场景

我们都会碰到这样的情况,某某用户临时又增加新的校验规则需求,但是需求又很碎很急,没法等到下一个版本上线(想打人有木有),这种时候如果为了上线该改动需要重启服务,修改代码,除了增加开发工作量以外还增大了服务运维风险。我们当然可以使用类似weblogic之类支持热部署的服务器,但是这对大部分公司显然不适用。
如果这个时候能够将代码逻辑配置到数据库中,让运维人员编写简单的逻辑即可满足需求而不需要大动干戈岂不是极好?所以这里我引入了luaj脚本这一概念。

技术介绍

lua是一种轻量级、支持交互式式编程的脚本语言,在redhat、centos中都有自带。
luaj即为LuaJavaBridge,提供与Java互相嵌入的支持。

交互调用

判断规则仍然利用lua语言实现,存入数据库中,java首先从数据库中读出判断脚本然后利用luaj ScriptEngineManager执行脚本,脚本中接受从java传入的json字符串参数,调用第三方lua解析为json对象,进行逻辑判断后返回结果(json字符串)。在luaj中只能使用.lua文件,无法调用.so的C库。

业务场景是根据不同的产品类型(测试列子一对一)获取对应的校验规则脚本,并执行脚本。

一、maven依赖

<!-- luaj -->
<dependency>
	 <groupId>org.luaj</groupId>
	 <artifactId>luaj-jse</artifactId>
	 <version>3.0.1</version>
</dependency>

二、建立规则表

规则表 t_test_rule

CREATE TABLE `t_test_rule` (
  `shell` varchar(5000) NOT NULL COMMENT 'lua脚本',
  `set_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `type` int(1) DEFAULT '0' COMMENT '产品类型',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

三、编写校验脚本

(demo)

package.path="F:/?.lua" --引入第三方lua,用于处理json,在luaj模式下只能使用.lua文件而无法使用编译好的.so库
local dkjson = require("dkjson")
local str = "{\"code\":0,\"msg\":\"success!\"}"
local retObj,pos,err = dkjson.decode(str , 1, nil)--解析json字符串
if(myParams==nil)
then
  retObj.code=1
  retObj.msg="入参丢失!"
  local retStr = dkjson.encode(retObj)
  return retStr
end


local jsonParams,jpos,jerr = dkjson.decode(otaParams , 1, nil)
if(jsonParams==nil)
then
  retObj.code=1
  retObj.msg=err
  local retStr = dkjson.encode(retObj)
  return retStr
end

if(jsonParams.version~=nil) then
 local sversion = string.gsub(version,"%p","")--lua字符串过滤,去除.
 local num = tonumber(sversion)
 if(num <= 231) 
  then 
   retObj.code=1 
   retObj.msg="版本低于2.3.1"
 end
end

local retStr = dkjson.encode(retObj)
return retStr

java调用,这里使用的是jfinal框架

public static boolean checkTheRule(Map<String,String> map, String params) {
		boolean flag = true;
		String type = map.get("type") + "";
        //规则查询
		List<Record> shells = Db.find("SELECT t.shell from t_test_rule t where type = ?",type);
		
		if (shells != null) {
			for (Record tmp : shells) {
				String shell = tmp.getStr("shell");
				ScriptEngineManager mgr = new ScriptEngineManager();
				ScriptEngine e = mgr.getEngineByName("luaj");

				e.put("myParams", params);//参数传入,这里也可以传入java类,成为在lua中调用执行的方法,比如mysql连接封装方法
				try {
					String resultMsg = e.eval(shell)+"";//执行并获取返回结果
					if(resultMsg==null || resultMsg==""){
						log.info("lua脚本执行返回空 ");
						flag = false;
						break;
					}
					RuleResult retObj = JSON.parseObject(resultMsg, RuleResult.class);
					if(retObj!=null){
						if(retObj.getCode()==0){
							flag = true;
						}else{
							flag = false;
							break;
						}
						log.info(retObj.getMsg());
					}else{
						log.info("json转换失败");
						flag = false;
						break;
					}
				} catch (ScriptException e1) {
					log.info("error:"+e1);
					flag = false;
					break;
				}
			}
		} else {
			log.info("没有找到"+type+"对应的规则!");
			flag = true;
		}
		return flag;
	}

测试入口类

@Test
	public void testPrint(){
		initConfig();
		Map map = new Hashtable();
		map.put("type", "38");
		map.put("version", "Ld2017");
		String params =  "{\"version\":\"2.3.0\"}";
		try {
			boolean flag = checkTheRule(map ,params);
			System.out.println(flag);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

四、运行test结果

java动态执行service java动态执行代码平台_java动态执行service

后记

luaj中的lua脚本如果要使用第三方方法,可以使用以下两种方法:

  • 引入第三方lua文件
  • 直接将封装的Java方法传入引擎中

第三方lua引用,用于解析JSON

package.path="xxx/?.lua"
local dkjson = require("dkjson")

Java方法设置,用于mysql读取

/** 将java数据库查询方法丢入lua中使用 **/
e.put("readTable", new LuaMysqlConnector());
import java.util.List;

import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.TwoArgFunction;

import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;

/**
 * 用于luaj在lua脚本中执行mysql查询
 * @author zh
 *
 */
public class LuaMysqlConnector extends TwoArgFunction{

	@Override
	/*
	 * 入参lstatm 执行sql语句 retString 返回字段字符串;分割
	 * @see org.luaj.vm2.lib.TwoArgFunction#call(org.luaj.vm2.LuaValue, org.luaj.vm2.LuaValue)
	 */
	public LuaValue call(LuaValue lstatm, LuaValue retString) {
		/** 健壮性校验 **/
		if (lstatm == null || lstatm.tojstring().equals("")) {
			return LuaValue.valueOf("丢失查询sql");
		}
		if (retString == null
				|| retString.tojstring().equals("")) {
			return LuaValue.valueOf("丢失返回字段列表");
		}

		String[] retLabels = retString.tojstring().split(";");
		StringBuffer sb = new StringBuffer("[");
		List<Record> rets = Db.find(lstatm.tojstring());
		for (int r = 0; r < rets.size(); r++) {
			// 组装为json格式
			sb.append("{");
			for (int i = 0; i < retLabels.length; i++) {
				sb.append("\"");
				sb.append(retLabels[i]);
				sb.append("\":\"");
				sb.append(rets.get(r).getStr(retLabels[i]));
				sb.append("\"");
				if (i != (retLabels.length - 1)) {
					sb.append(",");
				}
			}
			if (r == (rets.size() - 1)) {
				sb.append("}");
			} else {
				sb.append("},");
			}

		}
		sb.append("]");
		return LuaValue.valueOf(sb.toString());
	}

}