起因
事情的起因源自上篇文章中源码分析的原因:要实现按需修改resolverFactory中缓存的参数,上篇及上上篇中为了实现功能添加了很多不必要的缓存,所以这篇文章中写一下我新的实现方法
改动点
去除对函数体的缓存
在上上篇文章中在缓存中保存了函数体,在新的解决方法中直接去掉就行了,因为不需要
添加对单个函数体的编译
由于采用的是按需添加缓存,但没有减少,所以当函数加入到resolverFactory的Map中后,除非是重启服务,否则只有全部清空,MapVariableResolverFactory中提供了clear()方法,下面是单个函数体编译的代码
public MapVariableResolverFactory rebuildVariableResolverFactoryByStr(String expStr) {
MapVariableResolverFactory resolverFactory = new MapVariableResolverFactory();
ParserContext ctx = new ParserContext();
try{
char[] charArr = expStr.toCharArray();
MVEL.eval(charArr, ctx, resolverFactory);
}catch (Exception e){
e.printStackTrace();
return null;
}
return resolverFactory;
}
实现逻辑
这里的实现逻辑是
- 从缓存中获取原来的resolverFactory
MapVariableResolverFactory resolverFactory = (MapVariableResolverFactory) getVariableResolverFactory();
- 将修改或新增的函数单独编译,调用上一步的编译方法
//newFunction.getFunctionEntity()为函数体,也就是函数的字符串
MapVariableResolverFactory newFuncResolverFactory = calculatorBuild.rebuildVariableResolverFactoryByStr(newFunction.getFunctionEntity());
-
进行相关参数的合并
这里进行3个参数的合并,下面一一说明:- 引用类合并
必须说明,在进行操作的时候一定要判断新编译的函数中是否存在引用类,不存在和跳过这一步,原理是:在mvel的编译行为进行的时候会对你传入的函数字符串进行扫描,如果发现import或import-static关键字就会生成ClassImportResolverFactory,并在这个类中保存引用的对象,以Map<String, Object> 的形式进行保存,其中Object为引用类的Class对象,在ClassImportResolverFactory中只提供了添加方法,不提供获取,代码如下,感兴趣的自行观看
这里的做法有点不讲究,应该和作者的观点相悖:使用反射直接获取dynImports中的参数,再用addClass()方法添加到缓存中的resolverFactory中,这里附上我的实现代码private Map<String, Object> dynImports; ... public Class addClass(Class clazz) { if (this.dynImports == null) { this.dynImports = new HashMap(); } this.dynImports.put(clazz.getSimpleName(), clazz); return clazz; }
private void addImportClass(VariableResolverFactory oldFactory, VariableResolverFactory newFactory) throws Exception{ ClassImportResolverFactory newClassImportResolverFactory = (ClassImportResolverFactory) newFactory.getNextFactory(); Field field = newClassImportResolverFactory.getClass().getDeclaredField("dynImports"); field.setAccessible(true); Map<String, Object> dynImports = (Map<String, Object>) field.get(newClassImportResolverFactory); for(String key : dynImports.keySet()){ Object value = dynImports.get(key); ((ClassImportResolverFactory) oldFactory.getNextFactory()).addClass((Class) value); } }
- VariableResolver合并
这个类MVEL提供了添加的方法,这里直接上实现
// 获取原resolverFactory中的Map Map<String, VariableResolver> resolverMap = resolverFactory.getVariableResolvers(); // 将新编译的resolverFactory中的Map放入 resolverMap.putAll(newFuncResolverFactory.getVariableResolvers()); // set回原来的Map中 resolverFactory.setVariableResolvers(resolverMap);
- resolver合并
这里只有一个需要注意的点,上面提到MVEL在编译的过程中会扫描传入的函数字符串,当找到function/def开头的内容时,会创建一个
PrototypalFunctionInstance类用以存放函数体相关的内容,再将函数体放到指定的resolverFactory中,放回的操作调用的是createVariable()方法,这个方法有两个实现:一个3参数,一个2参数,在resolver合并中必须使用3参数的,使用2参数的会导致合并后的数据不全,虽然第三个参数的传入值为null,下面附上代码MapVariableResolver newFuncResolver = (MapVariableResolver) newFuncResolverFactory.getVariableResolver(newFunction.getName()); // 获取新函数的函数实体 PrototypalFunctionInstance instance = (PrototypalFunctionInstance)newFuncResolver.getValue(); // 添加resolver到原resolverFactory resolverFactory.createVariable(newFunction.getName(), instance, null);
- 引用类合并
-
将resolverFactory放回缓存,操作参考上篇文章
源码中的关键字
最后在这里附上源码中存在的关键字
// 源码位置: AbstractParser
public static HashMap<String, Integer> loadLanguageFeaturesByLevel(int languageLevel) {
HashMap<String, Integer> operatorsTable = new HashMap();
switch(languageLevel) {
case 6:
operatorsTable.put("proto", 48);
case 5:
operatorsTable.put("if", 39);
operatorsTable.put("else", 40);
operatorsTable.put("?", 29);
operatorsTable.put("switch", 44);
operatorsTable.put("function", 100);
operatorsTable.put("def", 100);
operatorsTable.put("stacklang", 101);
case 4:
operatorsTable.put("=", 31);
operatorsTable.put("var", 98);
operatorsTable.put("+=", 52);
operatorsTable.put("-=", 53);
operatorsTable.put("/=", 55);
operatorsTable.put("%=", 56);
case 3:
operatorsTable.put("foreach", 38);
operatorsTable.put("while", 41);
operatorsTable.put("until", 42);
operatorsTable.put("for", 43);
operatorsTable.put("do", 45);
case 2:
operatorsTable.put("return", 99);
operatorsTable.put(";", 37);
case 1:
operatorsTable.put("+", 0);
operatorsTable.put("-", 1);
operatorsTable.put("*", 2);
operatorsTable.put("**", 5);
operatorsTable.put("/", 3);
operatorsTable.put("%", 4);
operatorsTable.put("==", 18);
operatorsTable.put("!=", 19);
operatorsTable.put(">", 15);
operatorsTable.put(">=", 17);
operatorsTable.put("<", 14);
operatorsTable.put("<=", 16);
operatorsTable.put("&&", 21);
operatorsTable.put("and", 21);
operatorsTable.put("||", 22);
operatorsTable.put("or", 23);
operatorsTable.put("~=", 24);
operatorsTable.put("instanceof", 25);
operatorsTable.put("is", 25);
operatorsTable.put("contains", 26);
operatorsTable.put("soundslike", 27);
operatorsTable.put("strsim", 28);
operatorsTable.put("convertable_to", 36);
operatorsTable.put("isdef", 47);
operatorsTable.put("#", 20);
operatorsTable.put("&", 6);
operatorsTable.put("|", 7);
operatorsTable.put("^", 8);
operatorsTable.put("<<", 10);
operatorsTable.put("<<<", 12);
operatorsTable.put(">>", 9);
operatorsTable.put(">>>", 11);
operatorsTable.put("new", 34);
operatorsTable.put("in", 35);
operatorsTable.put("with", 46);
operatorsTable.put("assert", 97);
operatorsTable.put("import", 96);
operatorsTable.put("import_static", 95);
operatorsTable.put("++", 50);
operatorsTable.put("--", 51);
case 0:
operatorsTable.put(":", 30);
default:
return operatorsTable;
}
}
会根据匹配到的关键字生成对应的实体类,如下
case 96:
this.st = this.cursor = this.trimRight(this.cursor);
this.captureToEOS();
ImportNode importNode = new ImportNode(this.expr, this.st, this.cursor - this.st, this.pCtx);
if (importNode.isPackageImport()) {
this.pCtx.addPackageImport(importNode.getPackageImport());
} else {
this.pCtx.addImport(importNode.getImportClass().getSimpleName(), importNode.getImportClass());
}
return this.lastNode = importNode;
case 99:
this.st = this.cursor = this.trimRight(this.cursor);
this.captureToEOS();
return this.lastNode = new ReturnNode(this.expr, this.st, this.cursor - this.st, this.fields, this.pCtx);
case 100:
st = this.cursor;
this.captureToNextTokenJunction();
if (this.cursor == this.end) {
throw new CompileException("unexpected end of statement", expr, st);
} else {
if (!ParseTools.isReservedWord(name = ParseTools.createStringTrimmed(expr, st, this.cursor - st)) && !ParseTools.isNotValidNameorLabel(name)) {
FunctionParser parser = new FunctionParser(name, this.cursor, this.end - this.cursor, expr, this.fields, this.pCtx, this.splitAccumulator);
Function function = parser.parse();
this.cursor = parser.getCursor();
return this.lastNode = function;
}
throw new CompileException("illegal function name or use of reserved word", expr, this.cursor);
}
第一次写的时候会写成缓存函数体这种操作是因为这里
public Object getReducedValueAccelerated(Object ctx, Object thisValue, VariableResolverFactory factory) {
PrototypalFunctionInstance instance = new PrototypalFunctionInstance(this, new MapVariableResolverFactory());
if (this.name != null) {
if (!factory.isIndexedFactory() && factory.isResolveable(this.name)) {
throw new CompileException("duplicate function: " + this.name, this.expr, this.start);
}
factory.createVariable(this.name, instance);
}
return instance;
}
每次进行编译的时候会检查原来的factory中是否存在同名的函数,如存在就报错,新的解决方法每次使用新的factory,所以避开了这个问题。
这次就这样!!