httl页面静态化模板帮助文档
更新时间:2023-12-19 01:43:01 阅读量: 教育文库 文档下载
- php页面静态化推荐度:
- 相关推荐
1.概述
HTTL(Hyper-Text Template Language)是一个高性能的JAVA开源模板引擎,适用于动态HTML页面输出,可替代JSP页面,它的指令和Velocity相似。
快速
将模板编译成JAVA字节码运行,并使用强类型推导,减少运行期反射和转型,渲染速度是Velocity,Freemarker等其它模板引擎的10倍,请参见第3章的性能对比。注:JSP只有Scriptlet是编译的,Tag和EL是解释执行的,而HTTL是全编译的。
简洁
保持最简洁指令集,只保留基本的占位/注释/转义,和八个必需的控制指令,其它都降级为表达式方法实现,比如:$!{include(\。
直觉
语法尽可能符合HTML和JAVA开发者的直觉,指令类似于常用Velocity指令,但改进了Velocity中不符合直觉的地方,请参见第5章与Velocity的对比。
友好
模板自描述,在模板内声明入参变量类型,并基于入参类型推导模板内所有变量类型,使IDE能方便的实现变量方法补全提示,对开发过程友好。
2.示例
2.1 调用示例
BooksServlet.java:
import httl.*;
import java.util.*;
Map
Engine engine = Engine.getEngine();
Template template = engine.getTemplate(\template.render(parameters, response.getWriter()); 注:缺省配置下,HTTL不依赖任何三方库,只需JDK1.5+即可。
2.2 模板示例
books.httl:
${book.title}
2.3 配置示例
httl.properties:
import.packages+=com.xxx
template.directory=META-INF/templates input.encoding=UTF-8 output.encoding=UTF-8
其中,+=表示追加配置,而不覆盖缺省配置。注意,所有配置项都有缺省值,如果使用缺省值,可以不配,更多配置参见文档第7章。
3.性能
3.1 性能对比
性能测试类:PerformanceTest.java 引擎 javahttl
模板
初始化 编译 首渲染 输出大小 十万渲染 每秒次数
1ms 0ms
28,778byte 8,739ms 11,442/s 28,778byte 9,608ms 10,407/s
books.java0ms
books.httl88ms 621ms 3ms
velocitysmarty4jbeetl
books.vm
21ms 96ms 23ms 28,172byte 41,969ms 2,382/s
freemarkerbooks.ftl103ms 121ms 141ms 27,571byte 56,192ms 1,779/s
books.st
2ms
75ms 18ms 29,044byte 65,855ms 1,518/s
books.btl24ms 11ms 159ms 26,820byte 81,428ms 1,228/s
环境:os: Mac OS X 10.8.2, cpu: 2 x 1.70GHz, mem: 4G, jvm: 1.7.0_09 ->
mem: 80M
HTTL的渲染速度接近于直接用Java硬编码输出,比其它模板引擎高出10倍。
HTTL用到的JDK的Compiler,编译一个类通常需要几百毫秒,比其它模板的编译要慢,但每个模板只在加载时编译一次。
注:不同环境的运行结果可能存在差异,以上测试数据仅供参考,你可以在自己的机器上执行测试:
git clone https://github.com/httl/httl.git cd httl
mvn test -Dtest=httl.test.PerformanceTest -Dstream=false -Dsize=200 -Dcount=10000
另外,HTTL缺省开启了EscapeXmlFilter,而其它模板引擎没有,为了公平,性能测试时配置关闭了Filter: value.filters=null
3.2 优化策略
3.2.1 强类型编译,并推导关联类型
对于表达式${user.name}的编译: 弱类型字节码生成:
Object user = context.get(\无法确定user是Map还是POJO // 反射获取属性的值,而且要运行期判断是user.getName(),还是user.name字段
Object name = ReflectUtil.get(user, \接下来name也要反射 HTTL强类型字节码生成:
User user = (User)context.get(\通过in=\声明类型 // 编译期通过User的字段类型推演name的类型,并在编译期决定使用getName()
String name = user.getName();
3.2.2 编译时就将文本编译成字节,加快输出
文本编译:
writer.write(\writer.write(user.getName()); 二进制编译:
output.write(new byte[] {60, 116, 97, 98, 108, 101, 62, 60, 116, 114, 62, 60, 116, 100, 62});
output.write(user.getName().getBytes());
这样可以大幅度降低IO输出时将文本通过字符集编码成二进制流的速度。 HTTL缺省每模板同时生成两份class,在用户传入OutputStream和Writer时,执行不同的class:
template.render(paramaters, outputStream); // 内部将执行二进制输出版本的Template类
template.render(paramaters, writer); // 内部将执行文本输出版本的Template类
3.2.3 对于赋值生成局部变量,不put回参数map
比如将:
${price} 编译成:
int price = price * discount / 100; write(price); 而不是:
context.put(\write(context.get(\
这样可以大量减少参数map的put和get的调用。
3.2.4 将文本不编译到字节码中,减少内存perm区占用,以及防止JIT失效:
当模板的内容较大时,会导致生成的字节码也比较大,字节码运行时会放在内存perm区,导致perm区过大。模板多时,用户可能需要不断调大perm区:java -XX:PermSize=256MB -XX:MaxPermSize=256MB
另外,SunJDK缺省对大于8K字节码的方法不进行JIT优化,我们常规开启的JVM是mixed模式的,即调用量大的,将由JIT编译成本地码运行,其它在JVM内解释执行,解释执行和编译执行的速度相差10倍以上。参见JVM的:globals.hpp product(bool, DontCompileHugeMethods, true,
\develop(intx, HugeMethodLimit, 8000,
\+DontCompileHugeMethods\
通过将文本不编译到字节码中,减少内存perm区占用,也能防止JIT失效。
3.2.5 减少反射调用,以及基本类型装箱。
因为模板输出的大量是基本类型和字符串,Httl遇到任何类似需要boxed和unboxed的地方,都会重载所有基本类型方法,以减少boxed和unboxed的处理。比如:当输出基本类型时,需要转成String,如果使用format(Object)接口,就会将基本类型装箱。
反射也经常是性能瓶颈所在,比如:因为int[]不继承于Object[],为了通用处理,改为Array.get(array, index)来获取数组的项,导致在Profiler分析时,发现80%的CPU时间都耗在Array.get()上。 等等。
4.设计
4.1 类关系
查看大图
4.1.1 核心领域模型划分原则:按实体域,服务域,会话域划分。
不管你做一个什么产品,都一定有一个被操作的主体,比如:服务框架管理的Service,任务框架管理的Task,Spring管理的Bean等,这就是实体域。 即然有被操作者,就一定有操作者,它管理被操作者的生命周期,发起动作,比如:服务框架的ServiceInvoker,,任务框架的TaskScheduler,Spring的BeanFactory等,这就是服务域。
服务域发起动作,在执行过程中,会有一些临时状态需要存储交换,比如:Invacation,Execution,Request等,这就是会话域。
相应的,在HTTL中:
Engine 为服务域,它是API的入口,并负责实体域Template的生命周期管理,它是Singleton实例的,加载后不可变,所以是线程安全的,它的初始化过程较重,请复用单例。
? Template 为实体域,代表着被操作者,它是Prototype实例的,即每个模板产生一个实例,加载后不可变,同样也是线程安全的,模板变化后,将产生不同的实例,而不改变原实例。 ? Context 为会话域,执有操作过程的可变状态,它是ThreadLocal实例的,即不和其它线程竞争使用,所以也是线程安全的,请不要跨线程传递,它的初始化轻量,每次执行新建实例,执行完即销毁。
?
这样划分的好处是,职责清晰,可变状态集中,每个域都是无锁线程安全的,保证在大并发下,不会降低系统的活性。
4.1.2 分包原则:按复用度,抽象度,稳定度分包。
复用度:每种用户所需用到的类,就是同一复用粒度的,比如:使用者和扩展者,这样可以减少代码干扰,以及最大化复用。 ? 抽象度:包中抽象类个数占比,比如包中有10个类,其中3个为抽象类(包括接口),则抽象度为3/10,保持被依赖者总是比依赖者的抽象度高,形成金子塔关系,这样可以保持每层都有足够的扩展性。
?
?
稳定度:被依赖包和依赖包的占比,如果一个包依赖很多包,那别的包变化都会引起它跟随变化,所以它就不稳定,反之即稳定,保持包的稳定度和抽象度成正比,即把抽象类(包括接口)放到稳定的包中,这样可以防止不稳定性传染。
稳定度与抽象度关系如下图:
也就是分包应该如下:
其中上面那个包不依赖其它包。所以它很稳定,应尽量把抽象类或接口放在这一层,
而下面那个包依赖了三个包,三个包变化会引起它跟随变化,所以它是不稳定,应尽量把具体实现类放在这一层。
因稳定度与抽象度成正比,所以不稳定度与抽象度成反比,计算方式如下: (1) I = Ce / (Ca + Ce)
I: Instability (不稳定度)
? Ca: Afferent Coupling (传入依赖,也就是被其它包依赖的个数) ? Ce: Efferent Coupling (输出依赖,也就是依赖其它包的个数)
?
(2) A = Na / Nc
A: Abstractness (抽象度)
? Na: Number of abstract classes (抽象类的个数) ? Nc: Number of classes (类的个数,包括抽象类)
?
(3) D = abs(1 - I - A) * sin(45) D: Distance (偏差)
? I: Instability (不稳定度) ? A: Abstractness (抽象度)
?
应该保持偏差越小越好,即下图所示交点都落在绿色反比线左右:
基于上面的原则,HTTL的包结构整体上划分为三层:
?
API (Application Programming Interface) 模板引擎的使用者依赖的接口类,也是核心领域模型,其中Engine类相当于微内核,只管理非功能性的扩展点的加载,不硬编码模板加载解析渲染的任何部分。
SPI (Service Provider Interface) 模板引擎的扩展者依赖的接口类,它依赖于API的领域模型,它是模板引擎功能正交分解的抽象层,以保证用户可以最小粒度替换需要改写的地方,方便二次开发。
? BUILT-IN (Built-in Implementation) 内置扩展实现,它是SPI标准实现,也是可被用户替换的类,它包含引擎所有做的事,包括扩展点之间的组装过程(可替换DefaultEngine),以确保没有功能换不掉,即平等对待扩展者。
?
采用子包依赖父包风格,所以将API放在根目录,SPI接口独立子包,各种实现放在SPI的下一级子包中。
使用者API导入: import httl.*; ? 扩展者SPI导入:import httl.spi.*;
?
下图是HTTL所有包的不稳定度与抽象度的比值距阵:
HTTL所有核心包都是靠近反比线的,即上图用绿色标识的,表示分包是合理的。
4.2 调用过程
查看大图
执行过程说明:(与上图中的序号对应) 1 当从引擎中获取模板时,
1.1 首先会在缓存查找是否已缓存,如果有缓存就直接返回, 1.2 如果没有,则加载模板, 1.3 接着进行模板语法解析,
1.3.1 在解析到表达式时,将其转译为Java表达式, 1.3.2 并对静态文本进行编译前过滤,比如删除空白等,
1.3.3 对解析后的Java代码进行编译,得到Template的具体模板实现类, 1.3.4 实例化模板实现类,
1.4 将模板实例写入缓存,并返回给用户, 2 当用户调用模板的渲染方法时,
2.1 将动态占位符内容,先格式化成字符串, 2.2 再进行过滤,比如转义动态内容的HTML特殊符, 2.3 然后输出过滤后的内容,如果是静态文本直接输出。
5.指令
基于Velocity指令和Html注释:
5.0 与Velocity对比
如果你用过Velocity模板,可以查看以下对比,加深了解:
5.0.1 语法对比
1. HTTL指令必需加注释外壳,只支持,不支持#if(x),确保不干扰HTML本身的有效源码。
2. HTTL指令中的变量不加$符,只支持,不支持,因为指令中没有加引号的字符串就是变量,和常规语言的语法一样,加$有点废话,而且容易忘写。 3. HTTL占位符必需加大括号,只支持${aaa},不支持$aaa,因为$在
JavaScript中也是合法变量名符号,而${}不是,减少混淆,也防止多人开发时,有人加大括号,有人不加,干脆没得选,都加,保持一致。 4. HTTL占位符当变量为null时输出空白串,而不像Velocity那样原样输出指令原文,即${aaa},等价于Velocity的$!{aaa},以免开发人员忘写感叹号,泄漏表达式源码,如需原样输出,可使用转义\\${aaa},在HTTL中,$!{aaa}表示不对内容进行过滤,用于原样输出HTML片段。
5. HTTL支持在所有使用变量的地方,进行表达式计算,也就是你不需要像Velocity那样,先#set($j = $i + 1),再#if($j == $m),可以直接,其它指令也一样。
6. HTTL采用扩展Class原生方法的方式,如:${\,而不像Velocity的Tool工具方法那样:$(StringTool.toChar(\,这样的调用方式更直观,更符合代码书写习惯。
5.0.2 指令对比
HTTL ${xxx.yyy} $!{xxx.yyy} \\# \\$ \\\\
Velocity $xxx.yyy ${xxx.yyy} $!xxx.yyy $!{xxx.yyy} ## ... #* ... *# #[[ ... ]]# \\# \\$ \\\\ 不支持
#set($xxx = $yyy) #if($xxx == $yyy)
异
功能 同
相
输出占位符 同
一个是不过滤,不
一个是不原样输同
出 相
不显示注释块 似
相
不解析文本块 似
相
特殊符转义 同
不定义输入参数类同 型 相
给变量赋值 同
相
条件判断 同
相
否则条件判断 同
相
否则判断 同
相
结束指令 同
相
列表循环 同
#elseif($xxx == $yyy)
#else #end
#foreach($item in $list)
#if($xxx == $yyy) #break 相
中断循环
#end 似 #macro($xxx)
相宏替换,模板片
似 断
相捕获块输出到变似 量中
相读取文本文件内似 容
相包含另一模板输似 出 相
表达式求值 似
不停止模板解析
#define($xxx) $!{read(\$!{include(\$!{evaluate(\不支持
#include(\#parse(\#evaluate(\#stop
同
5.1 输出指令
5.1.1 过滤输出
输出表达式的计算结果,并进行过滤,比如:过滤变量中的HTML标签,防止HTML注入攻击。 格式:
${expression}
示例:
${user.name}
5.1.2 不过滤输出
原样输出表达式的计算结果,不进行任何过滤,通常用于输出HTML片段。 格式:
$!{expression}
示例: $!{body}
5.2 变量定义指令 5.2.1 类型声明
声明输入变量的类型,模板内部其它变量类型基于此类型推导。 格式:
示例:
5.2.2 变量赋值
将表达式的计算结果存入变量中。
格式:
示例:
缺省模板中set的变量,是不会回写到Context的参数Map中的。你需要用“:=”进行赋值,才会回写到Context的参数Map中: 格式:
示例:
你可以通过ThreadLocal的Context.getContext().getParameters()拿到回写的变量:
// 你可以把入参设成不可修写的Map,不会影响运行。 Map
Collections.unmodifiableMap(parameters);
// 传入的parameters在渲染过程中总是不会被修复,确保渲染过程无副作用,以及多次渲染的幂等性。
template.render(parameters, writer);
// 注意:这里获取到的并不是上面render参入的parameters,而是parameters的包装类。
// 此parameters是可写的,当模板中set回写变量时,写入该Map中。 // 在查询变量时,先在包装类中查找,找不到,再到原生传入的parameters中查找。
Context.getContext().getParameters().get(\
5.3 条件指令
5.3.1 IF条件
如果条件表达式计算结果为真或非空,则输出指令所包含的块。 格式:
示例:
5.3.2 ELSEIF条件
如果前面的IF条件不为真,并且当前条件表达式计算结果为真或非空,则输出指令所包含的块。
格式:
示例:
5.3.3 ELSE条件
如果前面的IF条件不为真,则输出指令所包含的块。 格式:
示例:
...
5.4 迭代指令
5.4.1 集合迭代
迭代表达式产生的集合,以集合中的每项值,重复输出指令所包含的块。
格式:
示例:
...
${foreach.index} ${foreach.size} ${foreach.first} ${foreach.last}
5.4.3 中断迭代
当条件表达式为真或非空时,中断当前迭代过程。 格式:
示例:
...
...
5.5 宏指令
将指令块封装成可复用的模板片断,在声明处不执行,在调用时再执行。 格式:
示例:
... $!{xxx(books)}
5.6 字面指令
5.6.1 注释块
隐藏注释的内容,用于注解过程,或屏蔽指令内容。 格式:
示例:
5.6.2 不解析块
原样输出模板内容,用于输出纯文本内容。 格式:
示例:
5.6.3 特殊符转义
原样输出指令特殊符,用于输出纯文本内容。 格式:
\\#, \\$, \\\\
示例:
\\${xxx}
6.表达式
基于Java表达式和扩展方法。
支持Java所有表达式,以下只列出与Java不同的点: 所有null值的操作均返回null。
? 所有实现Comparable的对象都支持比较运算符。 ? 所有对象都支持逻辑与或,分别返回空值或非空值。
?
6.1 操作符表达式
6.1.1 集合操作符
Sequence: 1..3 List: [123, \
Map: [\
6.1.2 日期操作符
date1 > date2 date1 >= date2 date1 < date2 date1 <= date2
6.1.3 逻辑操作符
等价于:
等价于:
等价于:
等价于:
6.2 函数表达式
6.2.1 转型函数
obj.to(\num.toDate
str.toDate
str.toDate(\str.toChar str.toBoolean str.toByte str.toInt str.toLong str.toFloat str.toDouble str.toClass
6.2.2 转义函数
str.escapeXml str.escapeUrl str.escapeString
6.2.3 格式化函数
num.format(\num.format(\date.format(\
date.format(\
6.2.4 集合函数
array[index] list[index]
map.key map[\
${item.xxx}
${item.xxx}
${entry.key}
${entry.value}
sort(list)
cycle(item, item)
${colors.next}
6.2.5 文件函数
include(\
include(\include(locale(\
read(\
read(\read(locale(\
6.2.6 系统函数
now() random() uuid()
7.配置
配置中,+=表示在缺省配置上追加配置,=表示覆盖缺省配置。
7.1 模板引擎配置
engine=httl.spi.engines.DefaultEngine parser=httl.spi.parsers.CommentParser
translator=httl.spi.translators.DfaTranslator
其中,engine负责组装,parser负责解析语法,translator负责将模板表达式翻译成java表达式。除非你想改变语法,或优化解析性能,否则此三项不需要配置。
如果你喜欢HTML标签属性语法,可以配置: parser=httl.spi.parsers.AttributeParser 语法如:
${book.title}