groovy
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyShell;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.kohsuke.groovy.sandbox.SandboxTransformer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* //Groovy 工具类: 用于在 java 中调用 groovy 脚本
* //在 java 中调用 groovy 脚本,有 4 种方式,这里采用的是 GroovyClassLoader 的方式
* // 1. ScriptEngineManager
* // 2. GroovyShell
* // 3. GroovyClassLoader
* // 4. GroovyScriptEngine
* //补充:
* // 1. groovy 脚本中不能通过 @Autowired 注入 springBean,可以通过 cn.hutool.extra.spring.SpringUtil#getBean(java.lang.Class) 的方式来使用 springBean
* //参考:
* // https://blog.csdn.net/feiqinbushizheng/article/details/108579530
* // https://zhuanlan.zhihu.com/p/395699884
* // http://www.xwood.net/_site_domain_/_root/5870/5874/t_c279152.html
*/
public class GroovyUtil {
// 注意:
// 系统每执行一次脚本,都会生成一个脚本的 Class 对象,这个 Class 对象的名字由 "script" + System.currentTimeMillis()+Math.abs(text.hashCode() 组成,即使是相同的脚本,
// 也会当做新的代码进行编译、加载,会导致 Metaspace 的膨胀,随着系统不断地执行 Groovy 脚本,最终导致 Metaspace 溢出
// 优化:
// 缓存 groovyClassLoader.parseClass 返回的对象,这样可以: 1. 解决 Metaspace 爆满的问题;2. 因为不需要在运行时编译加载,所以可以加快脚本执行的速度
//
private static final ConcurrentHashMap<String, Class> code2classMap = new ConcurrentHashMap<>();
// GroovyClassLoader 实例常驻内存,增加处理的吞吐量
public static GroovyClassLoader groovyClassLoader;
static {
/** --------- 沙箱环境配置 --------- */
// 编码规范和流程规范参考: https://zhuanlan.zhihu.com/p/395699884
// Groovy 会自动引入 java.util,java.lang 包,方便用户调用的同时,也增加了系统的风险.为了防止用户调用 System.exit 或 Runtime 等方法
// 导致系统宕机,以及自定义的脚本代码执行死循环或调用资源超时等问题,可以通过 SecureASTCustomizer,SandboxTransformer 对脚本进行检查
//
// <dependency>
// <groupId>org.kohsuke</groupId>
// <artifactId>groovy-sandbox</artifactId>
// <version>1.6</version>
// </dependency>
// 沙箱环境,若报错 java.lang.VerifyError ... Illegal use of nonvirtual function call,则更改 groovy 依赖版本为 2.4.7
// <dependency>
// <groupId>org.codehaus.groovy</groupId>
// <artifactId>groovy-all</artifactId>
// <version>2.4.7</version>
// </dependency>
CompilerConfiguration config = new CompilerConfiguration();
config.addCompilationCustomizers(new SandboxTransformer());
/** --------- 沙箱环境配置 --------- */
groovyClassLoader = new GroovyClassLoader(GroovyUtil.class.getClassLoader(), config);
}
/**
* 加载 GroovyObject 缓存(更新数据库中的 groovy 脚本后,需要调用该方法更新缓存)
*
* @param script 脚本源码
* @param scriptCode 脚本唯一编号
*/
public static void loadCache(String script, String scriptCode) {
Class groovyClass = groovyClassLoader.parseClass(script, scriptCode);
code2classMap.put(scriptCode, groovyClass);
}
/**
* 删除缓存(删除数据库中的 groovy 脚本后,需要调用该方法删除缓存)
*
* @param scriptCode 脚本唯一编号
*/
public static void removeCache(String scriptCode) {
code2classMap.remove(scriptCode);
}
/**
* 通过反射调用 groovy 脚本中定义的方法
*
* @param scriptCode 脚本唯一编号
* @param methodName 方法名称
* @param parameter 方法入参,参数类型和 groovy 脚本中定义的方法的入参类型需保持一致
* @return 调用 groovy 脚本中定义的方法的通用方法,为方便处理,这里将出参都强转为 Map<String, Object>,因此 groovy 中定义的方法出参,
* 也应该是 Map<String, Object>,否则会类型转换失败
*/
public static Map<String, Object> invokeMethod(String scriptCode, String methodName, Object parameter) {
Class clazz = code2classMap.get(scriptCode);
if (clazz == null) {
throw new RuntimeException("clazz is null");
}
Map<String, Object> result = new HashMap<>();
try {
// 将拦截器注册到当前线程
new NoSystemExitInterceptor().register();
new NoRunTimeInterceptor().register();
GroovyObject groovyObject = (GroovyObject) clazz.newInstance();
result = (Map<String, Object>) groovyObject.invokeMethod(methodName, parameter);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
/**
* 检查 groovy 语法是否正确
*
* @param sourceCode groovy 源码
*/
public static void checkGrammar(String sourceCode) {
GroovyShell groovyShell = new GroovyShell();
try {
groovyShell.parse(sourceCode);
} catch (CompilationFailedException e) {
throw new RuntimeException(e.getMessage());
}
}
}
点击查看代码
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@Component
public class GroovyService {
/**
* 初始化 GroovyObject 缓存
*
* @throws Exception
*/
//@PostConstruct
public void initCache() {
/**
* 模拟去数据库查询所有的 groovy 代码
*/
List<GroovyCodeEntity> groovyCodeEntities = new ArrayList<>();
// demo1
GroovyCodeEntity groovyCodeEntity_1 = new GroovyCodeEntity();
groovyCodeEntity_1.setId(1L);
groovyCodeEntity_1.setCode("demo1");
groovyCodeEntity_1.setName("示例1");
String scriptOfRefJavaAndGroovy =
" import java.time.LocalDate \n" +
"import com.lullaby.embg.groovy.demo.script.InsertData \n" +
" \n" +
"class RefJavaAndGroovy { \n" +
" \n" +
" void test(Map<String, Object> map) { \n" +
" // 引用 java 类 \n" +
" println(\"日期: \" + LocalDate.now()) \n" +
" // 引用 groovy 类 \n" +
" InsertData.insert() \n" +
" } \n" +
"} ";
groovyCodeEntity_1.setScript(scriptOfRefJavaAndGroovy);
groovyCodeEntities.add(groovyCodeEntity_1);
// demo2
GroovyCodeEntity groovyCodeEntity_2 = new GroovyCodeEntity();
groovyCodeEntity_2.setId(2L);
groovyCodeEntity_2.setCode("demo2");
groovyCodeEntity_2.setName("示例2");
// 建议: Groovy 脚本里面尽量使用 Java 静态类型,这样可以减少 Groovy 动态类型检查等,提高编译和加载 Groovy 脚本的效率
String scriptOfRefSpringBean =
" import cn.hutool.extra.spring.SpringUtil \n" +
"import com.lullaby.embg.groovy.demo.MyService \n" +
" \n" +
"class RefSpringBean { \n" +
" \n" +
" void test(Map<String, Object> map) { \n" +
" println(\"参数: \" + map) \n" +
" MyService myService = SpringUtil.getBean(MyService.class) \n" +
" myService.doSth() \n" +
" } \n" +
"} \n";
groovyCodeEntity_2.setScript(scriptOfRefSpringBean);
groovyCodeEntities.add(groovyCodeEntity_2);
// demo3
GroovyCodeEntity groovyCodeEntity_3 = new GroovyCodeEntity();
groovyCodeEntity_3.setId(3L);
groovyCodeEntity_3.setCode("demo3");
groovyCodeEntity_3.setName("示例3");
String scriptOfInsertData =
" import groovy.sql.Sql \n" +
" \n" +
"class InsertData { \n" +
" \n" +
" static void insert() { \n" +
" \n" +
" Sql sql = Sql.newInstance(\"jdbc:mysql://127.0.0.1:3306/demo\", \"user\", \"password\") \n" +
" \n" +
" sql.executeInsert(\"INSERT INTO tb_demo (id, code, name, other_info) VALUES (?, ?, ?, ?)\" \n" +
" , [1L, \"code-1\", \"name-1\", \"one\"]) \n" +
" \n" +
" sql.close() \n" +
" } \n" +
" \n" +
"} \n";
groovyCodeEntity_3.setScript(scriptOfInsertData);
groovyCodeEntities.add(groovyCodeEntity_3);
for (GroovyCodeEntity groovyCodeEntity : groovyCodeEntities) {
GroovyUtil.loadCache(groovyCodeEntity.getScript(), groovyCodeEntity.getCode());
}
}
}
点击查看代码
import cn.hutool.http.HttpUtil;
import org.junit.Test;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/groovy")
public class TestGroovyController {
/**
* 在 groovy 中调用 java 类/groovy 类
*/
@RequestMapping("/refJavaOrGroovy")
@Transactional
public String refJavaOrGroovy() {
// 脚本源码参看 GroovyService.initCache
GroovyUtil.invokeMethod("demo1", "test", null);
return "refGroovy 调用了";
}
/**
* 在 groovy 中调用 spring-bean
*/
@RequestMapping("/refSpringBean")
@Transactional
public String refSpringBean() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("a", "A");
parameters.put("b", "B");
// 脚本源码参看 GroovyService.initCache
GroovyUtil.invokeMethod("demo2", "test", parameters);
return "refSpringBean 调用了";
}
/**
* 事务(不支持在脚本之外控制事务)
*
* @return
*/
@RequestMapping("/tx")
@Transactional
public String tx() {
// 脚本源码参看 GroovyService.initCache
GroovyUtil.invokeMethod("demo3", "insert", null);
// 这里抛异常,并不会使得脚本中的事务回滚
if (1 == 1) {
throw new RuntimeException();
}
return "tx 调用了";
}
@Test
public void refJavaOrGroovyTest() {
String result = HttpUtil.get("http://localhost:8080/groovy/refJavaOrGroovy");
System.err.println(result);
}
@Test
public void refSpringBeanTest() {
String result = HttpUtil.get("http://localhost:8080/groovy/refSpringBean");
System.err.println(result);
}
@Test
public void txTest() {
String result = HttpUtil.get("http://localhost:8080/groovy/tx");
System.err.println(result);
}
}
import org.springframework.stereotype.Service;
@Service
public class MyService {
public void doSth() {
System.err.println(">>>>>>>>> service方法执行了");
}
}
import java.util.stream.Collectors
/**
* 闭包 "{}"
*/
class ClosureTest {
static void main(String[] args) {
List<String> list = Arrays.asList("a1", "a2", "b1", "b2")
// Finds the first value matching the closure condition.
String element = list.find { it -> it.startsWith("a") }
println("element: " + element)
List<String> elements = list.stream().filter { it -> it.startsWith("a") }.collect(Collectors.toList())
println("elements: " + elements)
}
}
点击查看代码
import org.kohsuke.groovy.sandbox.GroovyInterceptor;
public class NoRunTimeInterceptor extends GroovyInterceptor {
@Override
public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
if (receiver == Runtime.class) {
throw new SecurityException("不要在脚本中调用 RunTime 类的方法");
}
return super.onStaticCall(invoker, receiver, method, args);
}
}
import org.kohsuke.groovy.sandbox.GroovyInterceptor;
public class NoSystemExitInterceptor extends GroovyInterceptor {
@Override
public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
if (receiver == System.class && method.equals("exit")) {
throw new SecurityException("不要在脚本中调用 System.exit(int status) 方法");
}
return super.onStaticCall(invoker, receiver, method, args);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix