一、引言
最近作者的平台项目需要实现前端输入脚本,后端在用户设置好的一些情况下运行这段脚本。后端是java,所以我们采用Groovy脚本。
所以要实现的功能就是动态的Groovy脚本!
二、Groovy介绍
了解groovy和python的就可以直接到第三章了
2.1 优缺点
Groovy是一种基于Java虚拟机(JVM)的动态编程语言,它结合了静态类型语言和动态类型语言的特性。下面是Groovy的一些优点和缺点:
优点:
1、与Java的无缝集成
Groovy可以直接在Java代码中使用,与Java代码可以互相调用。这种无缝集成使得在现有Java项目中引入Groovy变得非常容易。
2、动态语言特性
Groovy支持动态类型、元编程和闭包等特性,使得代码编写更加灵活和简洁。它提供了更简洁的语法和更少的样板代码,提高了开发效率。
3、丰富的语法特性
Groovy提供了许多便利的语法特性,如集合操作、字符串处理、正则表达式等。它还支持DSL(领域特定语言)的编写,使得代码更易读、更具表达力。
4、可读性强
Groovy的语法设计力求简洁、易读,使得代码更加清晰和易于理解。它具有更少的冗余代码和更自然的表达方式。
缺点:
1、性能
相对于Java,Groovy的执行速度可能会慢一些。这是由于动态类型和动态特性的引入,以及Groovy的动态编译过程所导致的。然而,对于大多数应用程序而言,Groovy的性能仍然足够好。
2、工具和库支持
相对于Java,Groovy的工具和库生态系统相对较小。虽然Groovy可以直接使用Java的库,但有些库可能没有专门为Groovy进行优化或提供更高级的Groovy API。
2.2 Groovy和Python比较
Groovy和Python都是流行的动态编程语言,它们有一些相似之处,但也有一些区别。下面是Groovy和Python之间的一些比较
1、语法和风格
Groovy的语法与Java非常相似,因此对于Java开发者来说,学习和使用Groovy相对容易。它也支持类似Java的面向对象编程风格。
Python的语法更加简洁和优雅,具有非常清晰的语法结构。它采用了缩进来表示代码块,使得代码更易读。
2、类型系统
Groovy是一种动态类型语言,它允许变量在运行时动态地改变其类型。这使得Groovy更加灵活,但也可能导致一些类型相关的错误。
Python也是一种动态类型语言,但它具有更严格的类型系统。Python 3引入了类型提示(Type Hints)的功能,可以在代码中指定变量的类型,提高了代码的可读性和可维护性。
3、生态系统和库支持
Python拥有非常庞大和活跃的生态系统,有大量的第三方库和工具可供使用。这使得Python成为数据科学、Web开发、自动化脚本等领域的首选语言。
Groovy的生态系统相对较小,但它可以直接使用Java的库。对于Java开发者来说,Groovy可以无缝集成到Java项目中,享受Java生态系统的优势。
4、性能
通常情况下,由于其动态特性,Groovy的执行速度可能比Python慢一些。然而,对于大多数应用程序而言,这种差异并不明显,因为它们通常受限于I/O等其他因素。
Python通常被认为是一种相对较慢的语言,但通过使用C扩展和优化技术,可以提高Python的性能。
5、用途和应用领域
Python在数据科学、人工智能、Web开发、自动化脚本等领域具有广泛的应用。它拥有丰富的库和工具,如NumPy、Pandas、Django等,使得开发更加便捷。
Groovy在Java开发领域应用广泛,特别适用于构建DSL(领域特定语言)、编写脚本和快速原型开发。
三、动态脚本
1、pom
org.codehaus.groovy
groovy
3.0.8
2、前端
既然是动态的,就需要用户随意填写,即使有限制也是在后端进行权限之类的限制,然后向用户说明使用规则给用户提供一些基础类
所以前端只要给一个输入脚本的内容框就好
3、后端
3.1 运行groovy脚本
用户的脚本其实就是一个字符串,要运行这段字符串,怎么实现?
作者使用的是GroovyShell,看起来很简单,但是这只是开始,随着后面的拓展需要考虑很多细节
String script = param.getContent();
// 创建GroovyShell
GroovyShell shell = new GroovyShell();
// 运行Groovy脚本
Object response = shell.evaluate(script);
3.2 传入参数给脚本
第一个要考虑的就是一些参数可能是前面的逻辑带过来的,比如操作类型、订单、费用之类的,那就需要把这些参数带给脚本
// 创建绑定变量
Binding binding = new Binding();
binding.setVariable("orderId", orderId);
binding.setVariable("parameter", parameter);
// 创建GroovyShell
GroovyShell shell = new GroovyShell(binding);
// 运行Groovy脚本
Object response = shell.evaluate(script);
3.2 基础工具
脚本是要用来做逻辑的,不可能每次都是前面的逻辑把数据准备好,然后传递给脚本计算。
所以要把一些基础功能包装好,比如查数据库、调用接口
查数据库就用到之前的一篇文章了,通过动态的多数据源暴露出一个工具类,让脚本拿到这个类,指定数据库和sql就可以进行查询:
架构(十)Mybatis动态数据源及其原理_mybatis 动态数据源-CSDN博客
@Component
public class CommonGateWayImpl implements CommonGateWay {
@Resource
private CommonQueryMapper commonQueryMapper;
@Override
public List executeSql(String db, String sql) {
DataSourceThreadLocal.setDB(db);
List res = commonQueryMapper.query(sql);
DataSourceThreadLocal.clearDB();
return res;
}
}
调用接口基本上每家公司都不太一样,但是原理是相似的,都是根据系统标识找到一个可用的节点,拿到节点的url,调用这个url+方法,把请求参数转json作为消息体传过去
所以调用接口的工具类至少要有这两个方法,合并成一个也行,需要跟自己公司的注册中心打通一下,让中间件团队提供查询节点的功能。然后如果有权限校验的也要开一下,免得调用不通。
@Component
public class ExecuteInterfaceUtil {
public String getUrl(String systemCode, String action) {
List urlList = //查系统所有节点
if (CollectionUtilsExt.isBlank(urlList)) {
return null;
}
String url = urlList.size() > 0 ? urlList.get(0).getUrl() : "";
url = url + action;
return url;
}
public String execute(String url, String body, HashMap parameter) {
// 发送Http请求
return response;
}
}
3.3 SpringBoot取Bean
也是在写脚本执行的时候发现还需要一个功能,就是从SpringBoot的bean工厂取Bean,不然自己new一个对象是调用不了这里面的方法的,而且new也不优雅,这是基础工具又不是数据包装
所以就要找一下实现的方法,其实经常用的功能
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
// 应用上下文环境
private static ApplicationContext context;
@SuppressWarnings("static-access")
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
/**
* 根据bean name 获取对象
*
* @param name
* @return
*/
public static T getBean(String name) {
return (T)context.getBean(name);
}
/**
* 根据Class获取对象
*
* @param clazz
* @param
* @return
* @throws BeansException
*/
public static T getBean(Class clazz) throws BeansException {
return context.getBean(clazz);
}
}
4、脚本
底层都准备好了,那就可以写脚本试一试了,包名和导入的工具类路径都是必须的,groovy执行的时候都需要导入
这里演示了一下查完db数据之后,再去查接口明细
package ***.groovy
import ***.JSONUtil
import ***.StringUtilsExt
import ***.SpringUtil
import ***.ExecuteInterfaceUtil
import ***.CommonGateWay
import org.apache.curator.shaded.com.google.common.collect.Lists
CommonGateWay commonGateWay = SpringUtil.getBean(CommonGateWay.class)
ExecuteSoaUtil executeInterfaceUtil = SpringUtil.getBean(ExecuteSoaUtil.class)
List dataList = commonGateWay.executeSql("orderDb", "SELECT TOP 10 orderId FROM order ORDER BY update_time DESC;")
if (dataList == null || dataList.size() == 0) {
return dataList
}
String url = executeInterfaceUtil.getUrl("abc", "getDetail")
if (url == null || url.length() == 0) {
return dataList
}
List res = new ArrayList()
for (HashMap map : dataList) {
HashMap body = new HashMap(2)
body.put("orderIdList", Lists.newArrayList(map.get("orderid")))
String response = executeInterfaceUtil.execute(url, JSONUtil.toJson(body), null)
if (StringUtilsExt.isBlank(response)) {
return res
}
HashMap interfaceMap = JSONUtil.parse(response, HashMap.class)
res.add(soaMap)
for (Map.Entry entry : interfaceMap.entrySet()) {
Object value = entry.getValue()
if (!(value instanceof Integer) && !(value instanceof String)) {
entry.setValue(JSONUtil.toJsonNoException(value))
}
}
if (!interfaceMap.containsKey("responseCode") || (int) interfaceMap.get("responseCode") != SUCCESS) {
return res
}
}
return res
再看看执行结果
四、总结
动态的脚本这样就实现了,其实用python也可以,java也有支持python的执行库,不过用起来就有点曲折了。
即使用得是和java无缝集成的Groovy,过程也是有点绕弯的,而且还有一些小坎没写,写出来会降低作者的逼格,毕竟很多时候调试开发遇到的都是一些很小的问题一下子没转过来,事后想想又觉得脑子丢了一会。
有兴趣的同学可以在评论区和作者讨论自己项目的疑难杂症!一起交流进步