java-mvel使用记录3——缓存刷新问题

本文记录了在Java MVEL中遇到的缓存刷新问题及其解决方案。通过去除对函数体的缓存,添加单个函数体的编译,实现了按需修改resolverFactory的功能。详细介绍了实现逻辑,包括引用类合并、VariableResolver合并和resolver合并的过程,以及源码中的关键字解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

起因

事情的起因源自上篇文章中源码分析的原因:要实现按需修改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中只提供了添加方法,不提供获取,代码如下,感兴趣的自行观看
      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;
      }
      
      这里的做法有点不讲究,应该和作者的观点相悖:使用反射直接获取dynImports中的参数,再用addClass()方法添加到缓存中的resolverFactory中,这里附上我的实现代码
       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,所以避开了这个问题。


这次就这样!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值