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

前言

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<Listener> ls = new ArrayList<Listener>();
// 所有注册的方法
Map<String, Function> fnMap = new HashMap<String, Function>();
// 格式化函数
Map<String, Format> formatMap = new HashMap<String, Format>();
Map<Class, Format> defaultFormatMap = new HashMap<Class, Format>(0);
// 虚拟函数
List<VirtualAttributeEval> virtualAttributeList = new ArrayList<VirtualAttributeEval>();
Map<Class, VirtualClassAttribute> virtualClass = new HashMap<Class, VirtualClassAttribute>();
// 标签函数
Map<String, TagFactory> tagFactoryMap = new HashMap<String, TagFactory>();
ClassSearch classSearch = null;
// java调用安全管理器
NativeSecurityManager nativeSecurity = null;
ErrorHandler errorHandler = null;
Map<String, Object> 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 构建暴露特定方法的自定义缓存管理器。

Beetl 源码解析:GroupTemplate 类的更多相关文章

  1. [Java源码解析] -- String类的compareTo(String otherString)方法的源码解析

    String类下的compareTo(String otherString)方法的源码解析 一. 前言 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识,  ...

  2. lucene原理及源码解析--核心类

    马云说:大家还没搞清PC时代的时候,移动互联网来了,还没搞清移动互联网的时候,大数据时代来了. 然而,我看到的是:在PC时代搞PC的,移动互联网时代搞移动互联网的,大数据时代搞大数据的,都是同一伙儿人 ...

  3. Log4j源码解析--Layout类解析

    本文转载上善若水的博客,原文出处:http://www.blogjava.net/DLevin/archive/2012/07/04/382131.html.感谢作者的分享. Layout负责将Log ...

  4. junit源码解析--核心类

    JUnit 的概念及用途 JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个开源的单元测试框架.它属于白盒测试,只要将待测类继承 TestCase 类,就可以利用 JUnit ...

  5. java源码解析——Stack类

    在java中,Stack类继承了Vector类.Vector类和我们经常使用的ArrayList是类似的,底层也是使用了数组来实现,只不过Vector是线程安全的.因此可以知道Stack也是线程安全的 ...

  6. Spring源码解析——核心类介绍

    前言: Spring用了这么久,虽然Spring的两大核心:IOC和AOP一直在用,但是始终没有搞懂Spring内部是怎么去实现的,于是决定撸一把Spring源码,前前后后也看了有两边,很多东西看了就 ...

  7. muduo源码解析11-logger类

    logger: class logger { }; 在说这个logger类之前,先看1个关键的内部类 Impl private: //logger内部数据实现类Impl,内部含有以下成员变量 //时间 ...

  8. muduo源码解析10-logstream类

    FixedBuffer和logstream class FixedBuffer:noncopyable { }: class logstream:noncopyable { }: 先说一下包含的头文件 ...

  9. Log4j源码解析--核心类解析

    原文出处:http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html.感谢上善若水的无私分享. 在简单的介绍了Log4J各个模块类的作用 ...

  10. volley源码解析-Throwable类源码解析

    前提知识点: 1.Serializable接口 作用:表示可序列化的语义.就是Java提供的通用数据保存和读取接口.任何类型实现了Serializeable接口,就可以被保存到文件中,或者作为数据流通 ...

随机推荐

  1. deepin版的微信无法启动

    我在网上找了半天的了,出现问题的原因应该是deepin-wine的问题: 既然是deepin-wine出了问题,那重装一下就好了,嗯~ 我直接,yay!结果发现有一个deepin-wine需要更新,更 ...

  2. 用户不在 sudoers 文件中。此事将被报告

    在终端,进入root模式 vim /etc/sudoers 在    sudo (ALL:ALL) ALL下 添加 用户名 (ALL:ALL) ALL

  3. MYSQL 是如何保证binlog 和redo log同时提交的?

    MYSQL 一个事务在提交的时候能够保证binlog和redo log是同时提交的,并且能在宕机恢复后保持binlog 和redo log的一致性. 先来看看什么是redo log 和binlog,以 ...

  4. 个人呕心沥血编写的全网最详细的kettle教程书籍

    笔者呕心沥血编写的kettle教程,涉及到kettle的每个控件的讲解和详细的实战示例 可以说是全网最详细的kettle教程,三天学完你就可以成为优秀的ETL专家!!! 现在免费分享出来!视频教程也已 ...

  5. linux中ping命令停不下来解决方案

    linux的 ping 命令和windows不一样.windows默认只发送四个包的. 你可以使用ping -c 4 [ip/域名]这种方式来实现你想要的. linux控制台程序一般强制终止都是Ctr ...

  6. 【AtCoder Beginner Contest 330)】[E - Mex and Update ] 线段树+二分

    本题可以用线段树+二分的方式实现.代码如下: import java.io.IOException; import java.io.InputStreamReader; import java.io. ...

  7. YCProgress自定义百分比进度条

    目录介绍 1.本库优势亮点 2.使用介绍 2.1 圆环百分比进度条 2.2 直线百分比进度条 2.3 仿杀毒类型百分比进度条 3.注意要点 4.效果展示 5.其他介绍 1.本库优势亮点 圆环百分比进度 ...

  8. [Oracle]细节、经验

    [版权声明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/131054454 出自[进步* ...

  9. Java13版本特性【一文了解】

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  10. 一文搞懂JavaEE的接口

    在Java EE(Java Enterprise Edition)开发环境中,编程意义上的"接口"(interface)和API接口虽然都涉及接口的概念,但它们属于不同层面的术语. ...