Open TFdream opened 2 years ago
Groovy是一门基于JVM的动态语言,同时也是一门面向对象的语言,语法上和Java非常相似。它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。
添加依赖:
<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>3.0.8</version> </dependency>
按照JSR223,使用标准接口ScriptEngineManager调用。
import javax.script.*; import java.util.Date; public void scriptEngine() throws ScriptException, NoSuchMethodException { ScriptEngineManager factory = new ScriptEngineManager(); // 每次生成一个engine实例 ScriptEngine engine = factory.getEngineByName("groovy"); Bindings binding = engine.createBindings(); // 入参 binding.put("date", new Date()); // 如果script文本来自文件,请首先获取文件内容 engine.eval("def getTime(){return date.getTime();}", binding); engine.eval("def sayHello(name,age){return 'Hello,I am ' + name + ',age' + age;}"); // 反射到方法 Long time = (Long) ((Invocable) engine).invokeFunction("getTime", null); System.out.println(time); String message = (String) ((Invocable) engine).invokeFunction("sayHello", "zhangsan", 12); System.out.println(message); }
Groovy官方提供GroovyShell,执行Groovy脚本片段,GroovyShell每一次执行时代码时会动态将代码编译成Java Class,然后生成Java对象在Java虚拟机上执行,所以如果使用GroovyShell会造成Class太多,性能较差。
final String script = "Runtime.getRuntime().availableProcessors()"; Binding intBinding = new Binding(); GroovyShell shell = new GroovyShell(intBinding); final Object eval = shell.evaluate(script); System.out.println(eval);
Groovy官方提供GroovyClassLoader类,支持从文件、url或字符串中加载解析Groovy Class,实例化对象,反射调用指定方法。
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(); String helloScript = "package io.github.groovy.util" + // 可以是纯Java代码 "class Hello {" + "String say(String name) {" + "System.out.println(\"hello, \" + name)" + " return name;" "}" + "}"; Class helloClass = groovyClassLoader.parseClass(helloScript); GroovyObject object = (GroovyObject) helloClass.newInstance(); Object ret = object.invokeMethod("say", "github"); // 控制台输出"hello, github" System.out.println(ret.toString()); // 打印github
当JVM中运行的Groovy脚本存在大量并发时,如果按照默认的策略,每次运行都会重新编译脚本,调用类加载器进行类加载。不断重新编译脚本会增加JVM内存中的CodeCache和Metaspace,引发内存泄露,最后导致Metaspace内存溢出;类加载过程中存在同步,多线程进行类加载会造成大量线程阻塞,那么效率问题就显而易见了。
为了解决性能问题,最好的策略是对编译、加载后的Groovy脚本进行缓存,避免重复处理,可以通过计算脚本的MD5值来生成键值对进行缓存。
import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyObject; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author fengbingbing */ public class GroovyScriptCache { private static final Map<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>(); /** singleton **/ public static GroovyScriptCache getInstance() { return SingletonHolder.INSTANCE; } public GroovyObject newInstance(String script) throws InstantiationException, IllegalAccessException { return newInstance(script, GroovyScriptCache.class.getClassLoader()); } public GroovyObject newInstance(String script, ClassLoader parent) throws InstantiationException, IllegalAccessException { Class<?> groovyClass = CLASS_CACHE.get(script); if(groovyClass == null){ GroovyClassLoader loader = new GroovyClassLoader(parent); Class<?> tmpClass = loader.parseClass(script); CLASS_CACHE.putIfAbsent(script, tmpClass); //重新获取 groovyClass = CLASS_CACHE.get(script); } return (GroovyObject) groovyClass.newInstance(); } private static class SingletonHolder { private static final GroovyScriptCache INSTANCE = new GroovyScriptCache(); } }
zebra示例:
/** * * @author danson.liu, Dozer */ public class GroovyRuleEngine implements RuleEngine { private final GroovyObject engineObj; private static final Map<String, Class<?>> RULE_CLASS_CACHE = new ConcurrentHashMap<String, Class<?>>(); public GroovyRuleEngine(String rule) { try { engineObj = getGroovyObject(rule); } catch (Exception e) { throw new ZebraConfigException("Construct groovy rule engine failed, cause: ", e); } } @SuppressWarnings("resource") private static final GroovyObject getGroovyObject(String rule) throws IllegalAccessException, InstantiationException { if (!RULE_CLASS_CACHE.containsKey(rule)) { synchronized (GroovyRuleEngine.class) { if (!RULE_CLASS_CACHE.containsKey(rule)) { Matcher matcher = DimensionRule.RULE_COLUMN_PATTERN.matcher(rule); StringBuilder engineClazzImpl = new StringBuilder(200) .append("class RuleEngineBaseImpl extends " + RuleEngineBase.class.getName() + "{") .append("Object execute(Map context) {").append(matcher.replaceAll("context.get(\"$1\")")) .append("}").append("}"); GroovyClassLoader loader = new GroovyClassLoader(AbstractDimensionRule.class.getClassLoader()); Class<?> engineClazz = loader.parseClass(engineClazzImpl.toString()); RULE_CLASS_CACHE.put(rule, engineClazz); } } } return (GroovyObject) RULE_CLASS_CACHE.get(rule).newInstance(); } @Override public Object eval(Map<String, Object> valMap) { return engineObj.invokeMethod("execute", valMap); } }
public class DefaultDimensionRule extends AbstractDimensionRule { private static final String shardByMonthPrefix = "shardByMonth"; private static final String shardByLongPrefix = "shardByLong"; private RuleEngine dbRuleEngine; private RuleEngine tableRuleEngine; private List<DimensionRule> whiteListRules; private TableSetsManager tablesMappingManager; private Map<String, Set<String>> allDBAndTables = new HashMap<String, Set<String>>(); public DefaultDimensionRule(TableShardDimensionConfig dimensionConfig) { this.isMaster = dimensionConfig.isMaster(); this.needSync = dimensionConfig.isNeedSync(); this.tablesMappingManager = new DefaultTableSetsManager(dimensionConfig.getTableName(), dimensionConfig.getDbIndexes(), dimensionConfig.getTbSuffix(), dimensionConfig.isTbSuffixZeroPadding()); this.allDBAndTables.putAll(this.tablesMappingManager.getAllTableSets()); // for new unified rule and range String dbRuleStr = dimensionConfig.getDbRule(); String tbRuleStr = dimensionConfig.getTbRule(); if(dbRuleStr != null && (dbRuleStr.contains(shardByMonthPrefix) || dbRuleStr.contains(shardByLongPrefix))) { this.isRange = true; } this.dbRuleEngine = new GroovyRuleEngine(dbRuleStr); if(tbRuleStr != null && tbRuleStr.length() > 0) { this.tableRuleEngine = new GroovyRuleEngine(dimensionConfig.getTbRule()); } this.initShardColumn(dimensionConfig.getDbRule(), dimensionConfig.getTbRule()); } }
groovy
Groovy是一门基于JVM的动态语言,同时也是一门面向对象的语言,语法上和Java非常相似。它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。
1.在Java项目中集成Groovy
添加依赖:
1.1 ScriptEngineManager
按照JSR223,使用标准接口ScriptEngineManager调用。
1.2 GroovyShell
Groovy官方提供GroovyShell,执行Groovy脚本片段,GroovyShell每一次执行时代码时会动态将代码编译成Java Class,然后生成Java对象在Java虚拟机上执行,所以如果使用GroovyShell会造成Class太多,性能较差。
1.3 GroovyClassLoader
Groovy官方提供GroovyClassLoader类,支持从文件、url或字符串中加载解析Groovy Class,实例化对象,反射调用指定方法。
2.性能优化
当JVM中运行的Groovy脚本存在大量并发时,如果按照默认的策略,每次运行都会重新编译脚本,调用类加载器进行类加载。不断重新编译脚本会增加JVM内存中的CodeCache和Metaspace,引发内存泄露,最后导致Metaspace内存溢出;类加载过程中存在同步,多线程进行类加载会造成大量线程阻塞,那么效率问题就显而易见了。
为了解决性能问题,最好的策略是对编译、加载后的Groovy脚本进行缓存,避免重复处理,可以通过计算脚本的MD5值来生成键值对进行缓存。
解决方案
zebra示例:
参考资料