动态执行代码逻辑

动态执行逻辑的方法据我所知有一下两种方式

  • QLExpress
  • Groovy

QLExpress

QLExpress是阿里开源的动态脚本执行的项目。 由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。 在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。

https://github.com/alibaba/QLExpress

这种方案在配置上感觉不太方便,原因是没有IDE支持、某些JAVA语法不支持。。。

Groovy

来着百度百科

Groovy 是 用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。

Groovy是JVM的一个替代语言(替代是指可以用 Groovy 在Java平台上进行 Java 编程),使用方式基本与使用 Java代码的方式相同,该语言特别适合与Spring的动态语言支持一起使用,设计时充分考虑了Java集成,这使 Groovy 与 Java 代码的互操作很容易。(注意:不是指Groovy替代java,而是指Groovy和java很好的结合编程。

原理

通过Groovy提供的GroovyClassLoader把源代码动态加载编译成Class,Class再实例化成对象

动手实现

依赖

<dependency>
	<groupId>org.codehaus.groovy</groupId>
	<artifactId>groovy</artifactId>
	<version>3.0.0-rc-1</version>
</dependency>
<!--hutool 工具包,不是核心-->
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.0.3</version>
</dependency>
  1. 创建动态脚本工厂,inject方法用于扩展。
package cn.dhbin.dynamic;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import groovy.lang.GroovyClassLoader;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 动态脚本工厂
 * 作用:
 * 通过字符串源码生成Class
 * Class -> 实例
 *
 * @author donghaibin
 * @date 2019/11/19
 */
public class DynamicFactory {

	/**
	 * 单例
	 */
	private static DynamicFactory dynamicFactory = new DynamicFactory();

	/**
	 * groovy类加载器
	 */
	private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();

	/**
	 * 缓存Class
	 */
	private ConcurrentHashMap<String, Class<?>> classCache = new ConcurrentHashMap<>();

	/**
	 * 获取单例
	 *
	 * @return 实例
	 */
	public static DynamicFactory getInstance() {
		return dynamicFactory;
	}


	/**
	 * 加载创建实例,prototype
	 *
	 * @param codeSource 源代码
	 * @return 实例
	 * @throws Exception 异常
	 */
	public IScript loadNewInstance(String codeSource) throws Exception {
		if (StrUtil.isNotBlank(codeSource)) {
			Class<?> aClass = getCodeSourceClass(codeSource);
			if (aClass != null) {
				Object instance = aClass.newInstance();
				if (instance != null) {
					if (instance instanceof IScript) {
						this.inject((IScript) instance);
						return (IScript) instance;
					} else {
						throw new IllegalArgumentException(StrUtil.format("创建实例失败,[{}]不是IScript的子类", instance.getClass()));
					}
				}
			}
		}
		throw new IllegalArgumentException("创建实例失败,instance is null");
	}

	/**
	 * code text -> class
	 * 通过类加载器生成class
	 *
	 * @param codeSource 源代码
	 * @return class
	 */
	private Class<?> getCodeSourceClass(String codeSource) {
		String md5 = SecureUtil.md5(codeSource);
		Class<?> aClass = classCache.get(md5);
		if (aClass == null) {
			aClass = groovyClassLoader.parseClass(codeSource);
			classCache.putIfAbsent(md5, aClass);
		}
		return aClass;
	}


	/**
	 * 对script对象处理
	 *
	 * @param script {@link IScript}
	 */
	public void inject(IScript script) {
		// to do something
	}
}

  1. 定义脚本模板
package cn.dhbin.dynamic;

/**
 * 脚本接口,所有脚本实现该接口的{@link IScript#run(String)}方法
 *
 * @author donghaibin
 * @date 2019/11/19
 */
public interface IScript {

	/**
	 * 具体逻辑
	 *
	 * @param param 参数
	 * @return 执行结果
	 */
	String run(String param);

}

  1. 脚本执行器
package cn.dhbin.dynamic;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @author donghaibin
 * @date 2019/11/19
 */
public class ScriptExecutor {

	/**
	 * 缓存实例
	 */
	private ConcurrentHashMap<String, IScript> objCache = new ConcurrentHashMap<>();

	/**
	 * 执行脚本
	 *
	 * @param id 实例Id
	 * @return 运行结果
	 */
	public String run(String id, String param) {
		IScript script = objCache.get(id);
		if (script == null) {
			throw new IllegalArgumentException("未找到实例, id = [" + id + "]");
		} else {
			return script.run(param);
		}
	}

	/**
	 * 注册实例
	 *
	 * @param id 实例id
	 * @param script 实例
	 * @return 返回前一个实例,如果为null,则是新插入
	 */
	public IScript register(String id, IScript script) {
		return objCache.put(id, script);
	}

	/**
	 * 移除实例
	 *
	 * @param id 实例id
	 * @return 移除的实例
	 */
	public IScript remove(String id) {
		return objCache.remove(id);
	}


}

到这里,就基本实现了脚本的加载-实例化-执行。下面测试

编写脚本

package cn.dhbin.dynamic;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author donghaibin
 * @date 2019/11/19
 */
public class SimpleScript implements IScript{

	private static final Logger log = LoggerFactory.getLogger(SimpleScript.class);

	@Override
	public String run(String param) {
		log.info("输入的参数是:[{}]", param);
		log.info("你好世界");
		return "hello world";
	}

}

测试用例

package com.pig4cloud.pig.sms.dynamic;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

/**
 * @author donghaibin
 * @date 2019/11/19
 */
@Slf4j
class DynamicFactoryTest {

	@Test
	void runWithExecutor() throws Exception {
		DynamicFactory dynamicFactory = DynamicFactory.getInstance();
		ScriptExecutor executor = new ScriptExecutor();
		String codeSource = "package cn.dhbin.dynamic;\n" +
			"\n" +
			"import org.slf4j.Logger;\n" +
			"import org.slf4j.LoggerFactory;\n" +
			"\n" +
			"/**\n" +
			" * @author donghaibin\n" +
			" * @date 2019/11/19\n" +
			" */\n" +
			"public class SimpleScript implements IScript{\n" +
			"\n" +
			"\tprivate static final Logger log = LoggerFactory.getLogger(SimpleScript.class);\n" +
			"\n" +
			"\t@Override\n" +
			"\tpublic String run(String param) {\n" +
			"\t\tlog.info(\"输入的参数是:[{}]\", param);\n" +
			"\t\tlog.info(\"你好世界\");\n" +
			"\t\treturn \"hello world\";\n" +
			"\t}\n" +
			"\n" +
			"}\n";
		IScript script = dynamicFactory.loadNewInstance(codeSource);
		String id = "1";
		executor.register(id, script);

		for (int i = 0; i < 10; i++) {
			String result = executor.run(id, "abc");
			log.info("结果:[{}]", result);
		}

	}

	@Test
	void runWithoutExecutor() throws Exception{
		DynamicFactory dynamicFactory = DynamicFactory.getInstance();
		String codeSource = "package cn.dhbin.dynamic;\n" +
			"\n" +
			"import org.slf4j.Logger;\n" +
			"import org.slf4j.LoggerFactory;\n" +
			"\n" +
			"/**\n" +
			" * @author donghaibin\n" +
			" * @date 2019/11/19\n" +
			" */\n" +
			"public class SimpleScript implements IScript{\n" +
			"\n" +
			"\tprivate static final Logger log = LoggerFactory.getLogger(SimpleScript.class);\n" +
			"\n" +
			"\t@Override\n" +
			"\tpublic String run(String param) {\n" +
			"\t\tlog.info(\"输入的参数是:[{}]\", param);\n" +
			"\t\tlog.info(\"你好世界\");\n" +
			"\t\treturn \"hello world\";\n" +
			"\t}\n" +
			"\n" +
			"}\n";

		for (int i = 0; i < 10; i++) {
			IScript script = dynamicFactory.loadNewInstance(codeSource);
			String result = script.run("abc");
			log.info("结果:[{}]", result);
		}
	}


}

执行结果

11:19:32.243 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 输入的参数是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 结果:[hello world]

两个用例执行的结果都一样,区别就是一个使用了执行器。这样做的目的是提高运行效率,执行器缓存了实例对象,不用每次执行都实例化。

总结

Groovy这种方案其实是从xxl-job这个定时任务项目中提取出来的。它还扩展了Spring的几个注解,能从Spring的容器中加载Bean并使用。项目链接: https://gitee.com/xuxueli0323/xxl-job

SpringGlueFactory

思考

通过groovy动态加载Class,再结合Spring的生命周期,是否可以实现动态添加Bean?是否可以实现动态添加Controller?

image

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值