1、前言

上一章我们讲了LiteFlow动态规则如何使用,我比较好奇动态规则是如何加载到内存里面的,还有代码脚本如何生成到JVM,更难的一点是如何修改原本已经加载到JVM的Java脚本文件,会不会引起一些类加载冲突等问题。

2、动态规则和脚本实现原理

首先要提前说一下规则和脚本是两个不同的实现逻辑,规则是逻辑流转,语言脚本文件是内存对象。两个实现也是不一样的,所以分开说。按apollo的加载规则和脚本我画个流程图:
image-20240407190054551

以上的流程图简要的说明规则和脚本两条线的处理方式,其中

  • Apollo监听:由ApolloParseHelper.listenApollo()启动就会执行这个方法进行apollo config配置监听,apollo配置有什么风吹草动这个方法就会被触发到。
  • Chain的处理:由LiteFlowChainELBuilder.setEL(String elStr)这个环节进行,el表达是是通过第三方QLExpress来处理的,这个工具对el的支持简化了这类的逻辑实现。
  • Script的处理:由SpringDeclComponentParser.parseDeclBean(…) 该类处理,把ScripComponent处理得到的class给RootBeanDefinition rawClassDefinition = new RootBeanDefinition(clazz) Spring动态管理。
  • Java脚本处理:在ScriptComponent实现类里面JavaExecutor.load(String nodeId, String script),使用了第三方提供的类动态编译实现org.codehaus.commons.compiler.IScriptEvaluator(期间他有对类的包路径做了额外处理暂且不表)
		@Override
    public void load(String nodeId, String script) {
        try{
            IScriptEvaluator se = CompilerFactoryFactory.getDefaultCompilerFactory(this.getClass().getClassLoader()).newScriptEvaluator();
            se.setTargetVersion(8);
            se.setReturnType(Object.class);
            se.setParameters(new String[] {"_meta"}, new Class[] {ScriptExecuteWrap.class});
            se.cook(convertScript(script));
            compiledScriptMap.put(nodeId, se);
        }catch (Exception e){
            String errorMsg = StrUtil.format("script loading error for node[{}],error msg:{}", nodeId, e.getMessage());
            throw new ScriptLoadException(errorMsg);
        }

    }

3、最后

以上对整个常规使用Apollo昨晚第三方存储规则和脚本进行剖析,虽然说略微简单的说明整个实现的逻辑,但是在作者的开发过程中,对类处理和对语言脚本的支持逻辑肯定做了很多实验才能有这个结果,就比如Spring在管理的脚本对象后,是有动态代理的,你修改java脚本后触发Spring的刷新Node管理是否要再生成一个动态代理类?作者表示不需要,由于ComponentScanner中已经对原始类进行了动态代理,出来的对象已经变成了动态代理类,所以这时候的bean已经是NodeComponent的子类了,所以spring体系下,无需再对这个bean做二次代理。

整个类加载的过程是非常复杂的,所以作者建议少用动态脚本的方式,如果是固定的实现逻辑尽量写好java文件,改规则就好了,刷新class到JVM的过程可能对底层不深入了解的同学会带来一些难以预料的问题。

好了,下次在看有啥能接着讲的,比如EL或IScriptEvaluator这个第三方工具。

下一篇