• Beetl 源码解析:GroupTemplate 类


    本文首发于公众号:腐烂的橘子

    前言

    Beetl 是一款 Java 模板引擎,在公司的项目中大量运用,它的作用是写通用代码时,有一些差异化的逻辑需要处理,这时可以把这些差异化的逻辑写在模板里,程序直接调用,实现了代码的低耦合。

    有人问差异化的东西为什么不能通过配置实现?原因是配置只能将一些差异化的值抽离出来,一些复杂的逻辑很难做到。比如有一个类似计算器的界面,里面可以对一些业务字段进行公式计算:

    • 分润 = 利息 * 0.2
    • 分润 = (利息 + 罚息) * 0.1

    程序在计算这个表达式前,并不知道表达式的具体内容,只是想由业务传入利息、罚息这些字段上下文信息后,能自动计算出结果。这时使用 Beetl 模板,我们将上面的公式用 Beetl 表达式表示:

    • <%print(interest * 0.2);%>
    • <%print((interest + penalty) * 0.2);%>

    代码中就不用感知具体公式内容了,直接写通用逻辑即可:

    public static void main(String[] args) throws IOException {
    // 用户输入的公式
    String formula = "xxxx";
    // 计算结果的上下文参数
    HashMap param = new HashMap();
    // 核心代码
    GroupTemplate gt = new GroupTemplate(new StringTemplateResourceLoader(), Configuration.defaultConfiguration(););
    Template template = gt.getTemplate(formula);
    template.binding(new HashMap());
    // ans 就是计算的结果
    String ans = template.render();
    }

    以上就是 Beetl 的基本使用方法,具体可以参考文档,接下来的几篇文章,将对其源码进行解析,如有不合理的地方,欢迎各位大佬批评指正。

    核心类 GroupTemplate 源码解析

    核心字段定义

    GroupTemplate 类核心字段定义如下:

    public class GroupTemplate {
    /* 模板在运行过程中,class方法,accessory调用等需要的classLoader */
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader() != null
    ? Thread.currentThread().getContextClassLoader()
    : GroupTemplate.class.getClassLoader();
    AABuilder attributeAccessFactory = new AABuilder();
    ResourceLoader resourceLoader = null;
    Configuration conf = null;
    TemplateEngine engine = null;
    Cache programCache = ProgramCacheFactory.defaulCache();
    List ls = new ArrayList();
    // 所有注册的方法
    Map fnMap = new HashMap();
    // 格式化函数
    Map formatMap = new HashMap();
    Map defaultFormatMap = new HashMap(0);
    // 虚拟函数
    List virtualAttributeList = new ArrayList();
    Map virtualClass = new HashMap();
    // 标签函数
    Map tagFactoryMap = new HashMap();
    ClassSearch classSearch = null;
    // java调用安全管理器
    NativeSecurityManager nativeSecurity = null;
    ErrorHandler errorHandler = null;
    Map sharedVars = null;
    ContextLocalBuffers buffers = null;
    // 用于解析html tag得属性,转化为符合js变量名字
    AttributeNameConvert htmlTagAttrNameConvert = null;
    //...
    }

    核心字段解释如下:

    • classLoader: 模板在运行中需要的 ClassLoader
    • attributeAccessFactory: 一个工厂类,为一个特定类的方法生成 AttributeAccess,如果类是 Map,则生成 MapAA;如果类是 List, 则生成 ListAA 等
    • resourceLoader: 资源加载器,根据 GroupTemplate 的 key 获取对应资源的加载器,可以是文件、字符串等
    • conf: 模板配置,核心类,与 Beetl 相关的所有配置
    • engine: 模板引擎,只有一个 createProgram 方法,这个方法可以生成一个用来执行模板的程序
    • programCache: 本地缓存,基于 ConcurrentHashMap 实现
    • ls: 事件的监听器列表,暂时没用

    GroupTemplate 核心方法

    构造方法

    构造方法主要分为三步:

    1. 初始化默认配置
    2. 执行 init() 初始化方法,后面会讲
    3. 初始化资源加载器,实际上就是初始化 resourceLoader
    public GroupTemplate() {
    try {
    this.conf = Configuration.defaultConfiguration();
    init();
    initResourceLoader();
    } catch (Exception ex) {
    throw new RuntimeException("初始化失败", ex);
    }
    }

    init() 方法内部包含很多初始化行为,具体包括:

    1. 构建配置
    2. 初始化模板引擎
    3. 初始化自定义方法,即 beetl.properties 中配置的方法
    4. 初始化拓展资源,包括 formatMapdefaultFormatmap
    5. 初始化 tag,即配置里的 tagFactoryMap
    6. 初始化虚拟函数
    7. 初始化缓冲区

    init() 方法

    protected void init() {
    conf.build();
    engine = TemplateEngineFactory.getEngine(conf.getEngine());
    this.initFunction();
    this.initFormatter();
    this.initTag();
    this.initVirtual();
    this.initBuffers();
    classSearch = new ClassSearch(conf.getPkgList(), this);
    nativeSecurity = (NativeSecurityManager) ObjectUtil.instance(conf.getNativeSecurity(), this.classLoader);
    if (conf.errorHandlerClass == null) {
    errorHandler = null;
    } else {
    errorHandler = (ErrorHandler) ObjectUtil.instance(conf.errorHandlerClass, classLoader);
    }
    htmlTagAttrNameConvert = (AttributeNameConvert)ObjectUtil.instance(conf.htmlTagAttributeConvert, classLoader);
    }

    总结

    GroupTemplate 是 Beetl 模板的核心类,我们看到这个类非常重,里面包含了大量属性,这些属性都为后续模板计算提供相关资源。类中的大部分属性都使用了 HashMap 来存放上下文信息,这为我们设计一些类时提供了一定参考。其中缓存管理比较简单,基本就是封装了 ConcurrentHashMap 的方法,只对外暴露 4 个方法,这也是我们构建本地缓存管理的常用方式:基于 ConcurrentHashMap 构建暴露特定方法的自定义缓存管理器。

  • 相关阅读:
    Python tkinter - 第10章 文本控件(Text)文本位置索引
    高阶导数简介
    这3个扩展人脉小技巧,你还不来学?
    JNI基础知识总结
    社区买菜系统 毕业设计 JAVA+Vue+SpringBoot+MySQL
    记录一次macos没有sudoers文件问题
    又一关键系统上线,理想车云和自动驾驶系统登陆OceanBase
    SQL分层查询
    学习java的第三十一天。。。(网络编程)
    使用Docker部署Redis(单机部署)
  • 原文地址:https://www.cnblogs.com/rottenorange-cn/p/18153101