感谢作者:https://www.ab62.cn/article/36028.html
我的使用
场景:参数转换(适配上下游)
public static void main(String[] args) {String scriptText = script();GroovyClassLoader classLoader = new GroovyClassLoader();Class groovyClass = classLoader.parseClass(scriptText);try {GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();Object[] param = new Object[]{"{\"skuId\":\"835578\"}"};Object convert = groovyObject.invokeMethod("convert", param);System.out.println(JSON.toJSONString(convert));}catch (Exception e) {e.printStackTrace();}}private static String script() {return "package com.yonghui.yh.open.vop.adapt.service.convertor.groovy;\n" +"\n" +"import com.alibaba.fastjson.JSON;\n" +"import com.jd.open.api.sdk.request.vopsp.VopGoodsGetSkuDetailInfoRequest;\n" +"import com.yonghui.yh.open.vop.adapt.service.convertor.groovy.ConvertInterface;\n" +"\n" +"public class Converter implements ConvertInterface {\n" +" public String convert(String... manStr) {\n" +" //消息查询\n" +" VopGoodsGetSkuDetailInfoRequest request = new VopGoodsGetSkuDetailInfoRequest();\n" +" //上游传入的参数\n" +" ReqVopGoodsGetSkuDetailInfo req = JSON.parseObject(manStr[0], ReqVopGoodsGetSkuDetailInfo.class);\n" +" request.setSkuId(req.getSkuId());\n" +" request.setQueryExtSet(req.getQueryExtSet());\n" +" return JSON.toJSONString(request);\n" +" }\n" +"\n" +" //上游传入的商品信息\n" +" static class ReqVopGoodsGetSkuDetailInfo {\n" +" private long skuId;\n" +" private String queryExtSet;\n" +"\n" +" public long getSkuId() {\n" +" return skuId;\n" +" }\n" +"\n" +" public void setSkuId(long skuId) {\n" +" this.skuId = skuId;\n" +" }\n" +"\n" +" public String getQueryExtSet() {\n" +" return queryExtSet;\n" +" }\n" +"\n" +" public void setQueryExtSet(String queryExtSet) {\n" +" this.queryExtSet = queryExtSet;\n" +" }\n" +" }\n" +"}\n" +"\n";}
@Slf4j
@Service
public class GroovyConvert {private static final Map<String, List<TupleTwo<Integer, Object>>> SCRIPT_MAP = Maps.newConcurrentMap();@Autowiredprivate GroovyShell groovyShell;/*** 脚本转换,脚本中有RRException 抛出RRException异常,其他转换异常返回原始参数**/public String dynamicConvert(VopParamTypeEnum vopParamTypeEnum, String relationApiKey, String scriptContent, int maxscriptCont, String... paramStr) throws RRException {if (StringUtils.isBlank(relationApiKey) || StringUtils.isBlank(scriptContent) || paramStr == null || paramStr.length == 0) {return null;}String localCacheKey = relationApiKey+"_"+vopParamTypeEnum.getKey();try {Object converter = getExecutorByScript(localCacheKey, scriptContent, maxscriptCont);Class clazz = converter.getClass();Object[] args = new Object[]{paramStr};Class[] argsClass = new Class[args.length];for (int i = 0; i < args.length; i++) {argsClass[i] = args[i].getClass();}Method method = clazz.getMethod("convert", argsClass);method.setAccessible(true);String invokeResult = (String) method.invoke(converter, args);return invokeResult;} catch (InvocationTargetException e) {log.error("脚本异常", scriptContent, e);if (e.getCause() instanceof RRException) {RRException rrException = (RRException) e.getCause();throw new RRException(rrException.getCode(), rrException.getMessage());}} catch (Exception e) {log.error("脚本转换错误,script={},param={}", scriptContent, e);}return paramStr[0];}private Object getExecutorByScript(String localCacheKey, String scriptContent, int maxscriptCont) throws Exception {List<TupleTwo<Integer, Object>> tupleTwoList = SCRIPT_MAP.get(localCacheKey);if (CollectionUtils.isNotEmpty(tupleTwoList)) {for(TupleTwo<Integer, Object> tupleTwo :tupleTwoList){Integer localHashCode = tupleTwo.getFirst();//相等,才能使用if (localHashCode.equals(scriptContent.hashCode())) {return tupleTwo.getSecond();}}//如果未匹配到,传入的脚本数量不大于缓存的数量,则代表缓存数据存在问题,直接清理掉重新缓存if(maxscriptCont <=tupleTwoList.size()){tupleTwoList.clear();tupleTwoList =null;}}//注意:CopyOnWriteArrayListtupleTwoList = tupleTwoList == null?new CopyOnWriteArrayList<>():tupleTwoList;Class clazz = groovyShell.getClassLoader().parseClass(scriptContent);Object executor = clazz.newInstance();SpringContextUtils.autowireBean(executor);tupleTwoList.add(TupleTwo.of(scriptContent.hashCode(), executor));SCRIPT_MAP.put(localCacheKey,tupleTwoList);return executor;}public static void cleanLocalExecutor(String relationApiKey) {cleanLocalCache(relationApiKey+"_"+VopParamTypeEnum.PARAM_IN.getKey());cleanLocalCache(relationApiKey+"_"+VopParamTypeEnum.PARAM_OUT.getKey());}private static void cleanLocalCache(String localCacheKey) {List<TupleTwo<Integer, Object>> tupleTwoList = SCRIPT_MAP.get(localCacheKey);if(tupleTwoList != null){tupleTwoList.clear();tupleTwoList = null;}SCRIPT_MAP.remove(localCacheKey);}
}
是什么
Groovy 是一种基于 JVM 的动态语言,与 Java 语言紧密集成,可以很方便地在 Java 项目中使用。Groovy 有着简洁的语法、灵活的类型系统、强大的元编程能力,适合编写各种类型的脚本和应用程序。使用groovy也可以实现java程序的动态扩展,和用于插件化的开发,增强系统的可扩展性。
原理
其执行过程是:首先实例化一个GroovyClassLoader的对象,然后通过GroovyClassLoader 解析groovy脚本并生成一个Class文件,在然后实例化一个GroovyObject,通过GroovyObject执行脚本中的方法。
classloader的双亲委派,我们先来分析一下这个GroovyClassloader,看看它的祖先分别是啥:
groovy.lang.GroovyClassLoader@InnerLoader@42607a4f
groovy.lang.GroovyClassLoader@42e99e4a
sun.misc.Launcher.AppClassLoader@58644d46
sun.misc.Launcher.ExtClassLoader@62150f9e
Bootstrap ClassLoader ↑
sun.misc.Launcher.ExtClassLoader // 即Extension ClassLoader ↑
sun.misc.Launcher.AppClassLoader // 即System ClassLoader ↑
org.codehaus.groovy.tools.RootLoader // 以下为User Custom ClassLoader ↑
groovy.lang.GroovyClassLoader ↑
groovy.lang.GroovyClassLoader.InnerLoader
可以看到这种方式比较强大的,其通过类加载的方式进行脚本的加载和解析,使其在java中可以像调用java类的方法一样调用groovy脚本中的方法。因此GroovyClassLoader 适用于需要动态加载和执行 Groovy 脚本的场景,如插件化系统、动态扩展;也适用于一些需要在运行时动态生成和编译代码的场景。
比如我们可以定义个groovy类,其实现一个java接口,在java运行时我们可以将这个groovy加载到spring容器中,通过bean的方式使用groovy脚本。
在 Groovy 中,所有的 Groovy 类都是 Java 类的子类,包括通过 Groovy 脚本动态生成的类。因此,当使用 GroovyClassLoader 加载 Groovy 脚本并编译生成 Groovy 类后,得到的 Class 对象实际上也是 Groovy 类型的 Class 对象,可以被强制转换为 GroovyObject 类型。
GroovyObject 接口是所有 Groovy 类的基类,它定义了 Groovy 类的基本行为和属性。因此,只要一个类实现了 GroovyObject 接口,它就可以被认为是一个 Groovy 类。在 Groovy 中,所有的 Groovy 类都默认实现了 GroovyObject 接口,因此,当我们从 GroovyClassLoader 加载 Groovy 类后,可以直接将其强制转换为 GroovyObject 类型。
调用groovy脚本实现方式
1.使用GroovyClassLoaderprivate static void invoke(String scriptText, String function, Object... objects) throws Exception {GroovyClassLoader classLoader = new GroovyClassLoader();Class groovyClass = classLoader.parseClass(scriptText);try {GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();groovyObject.invokeMethod(function,objects);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}
2.使用ScriptEngine
private static final GroovyScriptEngineFactory scriptEngineFactory = new GroovyScriptEngineFactory();private static <T> T invoke(String script, String function, Object... objects) throws Exception {ScriptEngine scriptEngine = scriptEngineFactory.getScriptEngine();scriptEngine.eval(script);return (T) ((Invocable) scriptEngine).invokeFunction(function, objects);
}
3.使用GroovyShell
private static GroovyShell groovyShell = new GroovyShell();private static <T> T invoke(String scriptText, String function, Object... objects) throws Exception {Script script= groovyShell.parse(scriptText);return (T) InvokerHelper.invokeMethod(script, function, objects);
}