`
javamonkey
  • 浏览: 168799 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

现代模板引擎应具有的7大特征

 
阅读更多

  Java模板引擎已经发展了10余年,使用模板引擎者和模板引擎开发者都不思进取,得过且过,变化一直不大。毕竟,有技术含量值得屌丝追求的还是在后台。譬如早期的EJB,后来的WebService,SSH,还有现在的云计算,NOSQL等。但是,模板引擎没有变化并不代表已经非常成熟,它跟其他技术一样,也需要更新换代。程序员越来越认识到,模板开发在工作中占了较大工时,模板在渲染时占了CPU较大份额。现在国内外,已经有聪明的程序员意识到现在流行的模板引擎有些陈旧,不能满足更好的模板应用,纷纷提出新的模板引擎需求,甚至有些有才智的程序员已经开发出更好的模板引擎供大家使用。

这博文总结了本人长期实践以及来自其一线程序员的建议,列出了现代模板引擎应该具有的7大特征,并使用新一代Beetl模板语言作为说明。

 

  特征一,模板语言应该有较少的侵入性,以提高模板维护性

 

   由于模板语言总是“嵌入”到原来的文件中,减少对原有文件的侵入性对模板的维护是非常有利的。有的模板引擎的占位符号是“$变量名”,这就是一个典型侵入性过强的例子。因为对于html文件来说,一些js变量是以$开头(如Jquery),因此这样的模板即不好维护,而且模板引擎会误认为某些js变量也是占位符而导致解析混乱。

var userId = $userId;
 

    流行的Freemarker没有此问题,但更大的问题是它的控制语句定界符是"<#" ">",这样,进过它更改的HTML模板到处充满了<#符号,你再无法使用IE浏览器,Dreamweaver编辑工具进行编辑了。因为它不在是”HTML“了。
     还有些以HTML为主要对象的模板引擎,把控制语句作为DOM一个节点属性,这样看似对原有文件侵入性低,使用IE或者Dreamweaver仍然能打开编辑,但仍然混淆了模板语言和HTML文件。我以为仍然不利于模板的维护。

<tr item="user" foreach="usersList" >
</tr>

    为了降低模板语言对原有的文件侵入性,Beetl是如何做的呢?

    Beetl允许自定义占位符号和控制语句,譬如在HTML中,可以设定控制语句定界符是"<!--#" "-->",这在HTML中看似一个注释片段,因此侵入性就非常低了

 

<!--# for(user in usersList){ -->
<tr>
<td>${user.name}</td>
</tr>
<!--# } -->
 

你也可以设置自己喜欢的占位符,如最通用的"${" "}",或者我曾经喜爱的"~" "~"。

关于倾入性,还有个这样的有趣问题,就是自己开发一个界面生成工具,通常采用模板生成jsp代码,就是很少有人用”模板生成模板代码“,这是因为对一些陈旧的模板引擎来说,会产生符号冲突。Beetl中则不会出现此问题,其他模板语会有这个问题。

 

特征二, 性能良好才能省钱

 

   模板引擎渲染页面过程会占相当大部分的CPU,特别是一些后台只有少量计算的web应用,比如以数据库为中心的Web应用。选用高性能的模板引擎,是相当划算的选择,特别是对那些大的互联网公司,动不动就数百,数千台web服务器。省下一些CPU,就剩下一些钱。而且,高性能的模板引擎会给用户带来很好的Web使用体验。

  现在有才智的程序员已经把越来越多的提升模板引擎性能的方法应用于现代模板引擎,性能已经成倍的超过了原有的Freemarker,Velcoity。这些提升性能方法有:

  •   模板引擎将模板文件编译成class运行。
  •   模板中的静态部分采用二进制输出,不需要CPU运行的时候再转码
  •   合并模板中的静态部分一起输出,而不是每一行每一行输出

Beetl是为数不多同时具备上面三个优化策略的现代模板引擎.对于第一条编译成class,beetl比其他现代模板引擎走的更远一点,它是带类型的编译,尽量避免反射调用。而且,beetl不需求在模板中申明模板变量的Java类型,是通过运行时刻推测出来的,编译引擎非常先进。如下是一个模板

<html>
<body>
${user.name}/${user.role}<br/>
<%if(user.role == "admin"){%>
<table>
  <tr>
    <th>NO.</th>
    <th>Title</th>
    <th>Author</th>
    <th>Publisher</th>
    <th>PublicationDate</th>
    <th>Price</th>
    <th>DiscountPercent</th>
    <th>DiscountPrice</th>
  </tr>
  <%for(book in books){%>
  <%if(book.price > 0){%>
  <tr>
    <td>${book_index + 1}</td>
    <td>${book.title}</td>
    <td>${book.author}</td>
    <td>${book.publisher}</td>
    <td>${book.publication,dateFormat="yyyy-MM-dd HH:mm:ss"}</td>
    <td>${book.price}</td>
    <td>${book.discount}%</td>
    <td>${book.price * book.discount / 100}</td>
  </tr>
  <%}%>
  <%}%>
</table>
 

 编译后的代码

 

  final User user;
        final ArrayList<Book> books;
        try{
            user = (User)ctx.getVarWithoutException("user");
            books = (ArrayList<Book>)ctx.getVarWithoutException("books");
        }catch(ClassCastException ex){
            //转入解释模式执行
            throw new VaribaleCastException(ex);
        }
        try{
            out.write(__V0);
            out.write(user.getName());
            out.write(__V1);
            out.write(user.getRole());
            out.write(__V2);
            out.write(__VCR);
            if(user.getRole().equals("admin")){
                out.write(__V3);
                int book_index = 0;
                int book_size = books.size();
                for(Book book : books){
                    if((new BeeNumber(book.getPrice()).compareTo(new BeeNumber(0))>0)){
                        out.write(__V6);
                        out.write(__V7);
                        out.write((new BeeNumber(book_index).add(new BeeNumber(1))));
                        out.write(__V8);
                        out.write(__VCR);
                        out.write(__V9);
                        out.write(book.getTitle());

//// 忽略其他代码
 private static final byte[] __V13 = new byte[]{0x20,0x20,0x20,0x20,0x3c,0x74,0x64,0x3e};
    private static final byte[] __V14 = new byte[]{0x3c,0x2f,0x74,0x64,0x3e};
    private static final byte[] __V1 = new byte[]{0x2f};
    private static final byte[] __V15 = new byte[]{0x20,0x20,0x20,0x20,0x3c,0x74,0x64,0x3e};
    private static final byte[] __V0 = new byte[]{0x3c,0x68,0x74,0x6d,0x6c,0x3e,0xd,0xa,0x3c,0x62,0x6f,0x64,0x79,0x3e,0xd,0xa};
    private static final byte[] __V16 = new byte[]{0x3c,0x2f,0x74,0x64,0x3e};
    private static final byte[] __V3 = new byte[]

 

如代码4行所示,模板变量user即使User对象,这是模板运行时推测出来,无需再模板中申明

如代码11行,原有文件中的<html><body>俩行合并成一行输出。不像传统JSP,编译成class是多行输出,会影响性能的

代码32以后是实现将静态文本转化为二进制,这样,IO输出非常快!

 

 

Beetl通过采用上面三个策略,性能是以传统模板引擎的2-4倍。一些现代模板引擎,如HTTL,以及我的好友Green写的rythm也采用了上面三个优化策略,从它们的测试报告中,某些测试性能超过了5倍Freemarker。

 

 

特征三, 良好的错误提示

 

早期在使用JSP过程中,由于JSP运行出错,但找不到具体出错原因,这让我们老一批的开发人员非常痛苦。后来的模板引擎改善了这点,具有良好的错误提示,能精确的提示道错误的行数,和可能的错误原因。现在模板引擎在这点上做的更好,以Beetl为例,能显示错误的行数,错误的符号,错误的原因,以及错误所在的文件,和错误上下三行。(并且都是可定制的),譬如如下模板有某处错误:

<html>
<body>
${user.name}/${user.role}<br/>
<%if(user.role == "admin"{%>
<table>
  <tr>
    <th>NO.</th>

 在编译运行时,会报如下错误

>>语法错:缺少符号')',4 行 文件 \beetl\book.txt
1|<html>
2|<body>
3|${user.name}/${user.role}<br/>
4|<%if(user.role == "admin"{%>
5|<table>
6|  <tr>

 

这样,开发者就很容易改正问题

 

JSP之所以连错误行数都无法准确报告出来,主要是JSP会编译成Class而失去了原有行数信息,在现代模板引擎中,此信息不会丢失,譬如作者的好友Green的模板引擎能将行数保存到生成的源代码里,可以根据错误间接查询出模板错误行数,Beetl在这方面走的更棒,能自动在错误时打印出错误行数。这是因为beetl生成class代码的时候,将模板中可能出错的行数与生成的Class行数关联起来

 

 /* 行映射*/
    protected String lineMap = "-69=26-33=4-38=17-65=25-36=16-37=16-78=31-41=19-45=20-49=21-21=0-20=0-53=22-57=23-28=3-61=24-30=3-";

 以69=23为例子,表示class的69行出错,代表了模板中的26行出错

 

特征四, 支持渲染结果的再处理,实现灵活的模板功能

 

JSP标签库就是一种渲染结果再处理,譬如可以用JSP标签完成一个Cache标签。此标签可以缓存标签体的内容而不需要每次都去渲染。传统的模板引擎,如Freemarker也有这个功能。但某些速度很快的模板引擎却不具备此功能,这是很遗憾的。这样的模板除了作为代码生成器或者内容生成器外,并不适合作为动态Web输出。

 

Beetl 可以通过俩种方式支持渲染结果再处理,一种是类似JSP,Freemarker的标签库,以模板中的布局为例,beetl支持layout标签函数,可以轻易完成简单的布局功能,如下例子:

 

这是child页面
<%layout('/ext/layout_template.html'){%>
hello,我是${name}
<%}%>
 

 如代码所示,layout标签将使用/ext/layout_template.html作为布局页面,将”{“  ”}“的内容渲染后插入到模板页面

 

Beetl还有一种更为先进的渲染结果再处理的方式,即独一无二的模板变量功能,可以将模板渲染的结果作为模板变量保存起来以备用。如在复杂的布局要求中,通常要求模板页面的js部分插入到布局页面头部,动态html部分插入到布局页面的某处,采用别的模板引擎,这是几乎不可能实现的,使用Beetl,则很简单

<%
var jsPart = {
%>
<script>
//这是js部分,将放在布局页面的头部
</script>
<%};%>

<%var htmlPart={%>
<div>
这是html部分,将放在布局页面的底部
</div>
<%};%>

<%
var layoutParas = {"jsPart":jsPart,"htmlPart":htmlPart};
includeFileTemplate('/ext/complex_layout_template.html',layoutParas){}

%>
 

如上定义了俩个模板变量jsPart,和 htmlPart,渲染结果暂时保存到这俩个变量,然后再模板布局页面使用这俩个变量

 

特征五, 安全输出

 

   早期的JSP功能,经常因为空指针或者变量不存在而报错,这是缺少安全输出功能,Freemarker就做的特别好,可以用!表示如果变量为空或者不存在则输出!后面的值。Beetl也提供了同样的功能,如下模板中

<span>${user.wife.name!"单身"}</span>
<span>${user.wife.birDate!@com.xx.util.Constants.defaultData}</span>

 

上面例子第一行表示如果user为空,或者user.wife为空,或者user.wife.name为空,这输出”单身“。

第二行同样是安全输出,只是输出为java class的一个常量(在beetl中,直接调用class方法和属性是很简单的,只要加一个@符号即可,Beetl提供安全管理,架构师可以指定那些class能被调用,哪些class是不能调用,如Runtime类就不能被调用)

   遗憾的是,现代有些模板引擎还是不支持安全输出,只能说要么这些模板引擎的开发者还没有意识到安全输出的重要性,要么模板引擎还需要更长一段时间的完善

 

特征六,可测试的模板

 

   通常程序员测试模板是否正确,在MVC架构中,必须同时具备M V C 三个,只有很少模板引擎可以说只需要M 和 V,不需要C,现代模板引擎应该能做的更好,以Beetl为例,它支持仅有V的情况也能测试模板的正确性,这特别适合水平开发,即模板开发者,和后台开发者不是同一人的情况。

   Beetl是怎么做到的呢? 作为Beetl的作者,我花了很大精力才完成仅有V的情况下也能测试模板功能。譬如,如下简单模板

<span>this is template,${user.name},${sessions['userId']}</span>

 现在即没有User对象,也没有Web容器提供sessions,更没有控制层往session里设置一个userId属性,如何测试此模板呢?

   在Beetl中,提供SimpleTemplateTestUtil类用来完成模板测试,他提供俩个输入参数,第一个是模板,第二个是一个字符串,字符串包含了以json格式定义的变量,输出就是模板是否渲染无误,以及渲染结果

 

	String input = "this is template,${user.name},${sessions['userId']}";
		String json = "var user = {'name':'joel'},sessions={'userId':'12345'};";
		Writer w = new StringWriter();
		SimpleTemplateTestUtil util = new SimpleTemplateTestUtil(input, json, w);
		util.run();
		System.out.println(util.isOk());
		System.out.println(w);

 输出是

true
this is template,joel,12345

 

Beetl的参与者之一 ”一颗草“正在做一个beetl模板在线体验网站,即将完成,有兴趣的人可以再网站上在线写模板,测试模板效果,这正是基于Beetl提供的可测试模板,这是独一无二的。

 

特征七,简洁的指令,良好的扩展性:易学易用

 

   现代模板引擎语言,我以为应该以更简单的语法,丰富的函数调用为主。去掉那些花哨的语言特性为好。以循环为例子,最为简单的语法莫过于大家都熟悉的形式,如for(xxx in xxxList), 有的现代模板引擎则是for( xxx << xxxList),<<符号在Java中意为这移位操作,这很容易让人误解,其实真的无须这样语法,看似迷人,其实一点不好用。

   还有的模板引擎如Freemarker,"?" ,"!" 满天飞,一个小小的模板,充满了?! 符号,是非常别扭的。

   Beetl提供的语法非常简单,类似JS。能很快的上手,我采用Beetl的项目的同事们,基本上能做到不看Guild文档而能完成大部分模板的开发。Beetl同时又具备当今流行模板的所有功能(甚至在功能上是超过了这些模板),是通过提供了丰富的函数功能来完成的,以将日期格式转化输出为例子,Freemarker提供了如下多的选择

${openingTime?string.short}
${openingTime?string.medium}
${openingTime?string.long}
${openingTime?string.full}
 

而Beetl仅仅提供一个dateFormat格式化函数

 

${lastUpdated,dateFormat='yyyy-MM-dd'}

 

如果你不满意此日期格式函数,你也可以非常方便自定义格式化日期函数,如下代码

	group.registerFormat("shortDate", new Format(){

			@Override
			public Object format(Object data, String pattern)
			{
				Date d = (Date)data;
				return new SimpleDateFormat("yyyy-MM-dd").format(d);
			}
			
		}
		);
 

 你在模板中,可以调用shortDate格式化函数

${lastUpdated,shortDate} 

 

像Freemarker这样已经很容易学的模板引擎,我在群里,经常能看到很多人问起Freemarker各种使用问题,对于各位使用者来说,初学的时候仍然是花了很长时间,各种使用潜在的问题让初学者猝不及防。这都是因为Freemarker过多的语法,以及新手对一门新的语法产生的语法习俗 不适应导致的 ,我强烈介意那些头一次使用Freemarker的的程序员,谨慎考虑使用Freemarker带来的问题,尽管它是最流行的,但不代表对你的项目来说,是最理想的

 

    总结

 

 

十几年来,企业应用,互联网应用后端技术发生了很多变革,从分布式到Without EJB,从Database到Nosql,公司成本得到了很大降低。然而,对于架构师来说,同样至关重要前端技术,仍然没有变化,无非还是JSP技术,或 者采用模板引擎,如Freemarker,Velocity。前端对于与产品以及和作为产品实现者开发人员来来说,仍然是费时费力一块,君不见,错误的使 用前端技术,将抵消后台优化带来的用户体验,不适当的使用前端技术,将成倍增加开发和维护时间,尽管架构师们意识到到此问题,但可选方案几乎没有。这种现 象长时间未曾变化!

 

可喜的是,越来越多的现代模板引擎正在涌现出来,在国内,有我闲.大赋的Beetl,有梁飞的HTTL,有走向国际化的我的好友Green的rythm,更多的我就不一一列出来。这些模板引擎,或多或少的合乎现代模板引擎7大特征。必将在发展使用的几年后,取代陈旧,不思进取的老的模板引擎。也希望国人在选用模板引擎的时候,可以多多考虑国内的模板引擎。

 

 

 

 

分享到:
评论
5 楼 gdp8 2012-11-23  
干货,很强,作者很用心,支持!
4 楼 greenlaw110 2012-11-23  
大赋终于点燃模板引擎大战的硝烟了 借你的宝地宣传一下Rythm。

Rythm的定位是为Java开发人员提供易用,高性能以及强大功能的模板引擎,具有一下特点:

1. 强类型模板引擎,需要申明模板参数类型,同时支持Java的if-else, for loop和表达式。基本上写一个Rythm模板跟写Java类很相似。想看看具体代码的朋友可以打开http://rythmengine.com/,然后点击下面的“Template Source”,即可看到生成当前页面的模板源码。

2. 采用类似.net Razor引擎的简洁表达方式,只定义
@
一种特殊字符。参见http://www.playframework.org/modules/rythm-1.0.0-20120815a/user_guide

3. 提供简洁清晰的API:Rythm.render(String template, Object... params)。其中template可以使一个包含模板内容的字串,也可以使一个模板文件路径。params可以使一系列按照位置传递的参数,也可以使一个按照名字传递的Map<String, Object>对象

4. 非常丰富的模板重用机制,包括解析时模板引入,运行时模板调用,模板继承(很适合做layout重用)

5. 提供模板内缓存机制:参见http://rythmengine.com/demo/testcachehttp://rythmengine.com/demo/testtagcache

更多功能尽在线上演示:http://rythmengine.com/。每个演示都能点开查看模板源代码,方便你学习Rythm。

使用方法可以参考play-rythm模块的文档:http://www.playframework.org/modules/rythm-1.0.0-20120815a/user_guide。其中有小部分内容是play!framework特殊的,大部分都适用于将rythm引擎独立运行环境。
3 楼 zhongmin2012 2012-11-22  
收了,慢慢看,领悟
2 楼 zhongkaiuu 2012-11-22  
先收了,慢慢看
1 楼 nuoheng 2012-11-22  
很强大,很好用,支持

相关推荐

    一个强大的模板引擎拥有继承异步控制等等jinja2启发

    在这个场景中,我们关注的是一个受到Jinja2启发的模板引擎,它具有继承和异步控制等功能。 **Jinja2启发** Jinja2是Python编程语言中的一个强大模板引擎,以其简洁的语法和灵活性闻名。它受到了Python语法的启发,...

    Go-一个纯的GoLiquid模板引擎

    Go语言,作为一种高效、简洁且具有现代特性的编程语言,被广泛用于开发各种应用程序,包括Web框架、API服务器以及各种工具。在Web开发领域,模板引擎是不可或缺的一部分,它允许开发者将逻辑代码与呈现给用户的HTML...

    基于JavaScript和html的高性能模板引擎

    7. 性能测试和基准:为了确保模板引擎的高性能,开发者需要进行性能测试,对比不同模板引擎在相同场景下的表现。工具如 Benchmark.js 可以帮助进行性能基准测试。 8. 异步加载和模块化:考虑到现代Web应用的复杂性...

    企业标准的记者面板和模板引擎支持

    综上所述,企业标准的记者面板和模板引擎是现代新闻业务中不可或缺的组成部分。它们结合先进的技术和设计理念,为企业提供了高效、专业的工作环境,促进了新闻内容的高质量生产和分发。同时,理解并利用如Newscoop...

    基于template的HTML5模板引擎,安全无eval.zip

    通过熟练掌握fill.js这样的模板引擎,可以极大地提升开发效率,并保证网页的性能和安全性。 总的来说,基于template的HTML5模板引擎,如fill.js,是现代Web开发中不可或缺的工具,它使得动态内容的创建变得简单且...

    beetl模版引擎文档

    Beetl是一个新一代Java模板引擎,其版本为...总结而言,Beetl模板引擎以其全面的功能、直观的语法、优异的性能以及强大的整合性和个性化能力,成为了新一代Java模板引擎中的佼佼者,非常适合现代Web开发和模板应用。

    常用的JavaScript模板引擎介绍

    本文将介绍八款常用的JavaScript模板引擎,并特别讲解了artTemplate模板引擎,这是目前性能出众且支持模板编译的现代JavaScript模板引擎之一。 首先是Mustache,这是一款简单易用的模板引擎,其基于JavaScript实现...

    templatejs的webpakc编译loader一款javascript模板引擎

    在现代Web开发中,模板引擎扮演着至关重要的角色。它们允许开发者将数据和视图逻辑分离,使代码更易于维护和复用。Template.js是一个JavaScript模板引擎,它专注于提供高效且灵活的模板编译能力,用于生成HTML结构。...

    Discuz! freeNo7模板

    2. **响应式布局**:由于现代互联网设备多样化,freeNo7模板会具备响应式设计,能够自动适应不同屏幕尺寸的设备,如桌面、平板电脑和手机,确保在各种设备上都能提供良好的浏览体验。 3. **功能优化**:除了基础的...

    用于本机PHP模板PHP模板引擎-PHP开发

    Foil为现代PHP模板带来了现代模板引擎的所有灵活性和强大功能。 使用PHP编写简单,干净,简洁的模板。 FOIL PHP模板引擎,用于PHP模板。 Foil为现代PHP模板带来了现代模板引擎的所有灵活性和强大功能。 使用PHP编写...

    Velocity.js - 来自淘宝的 JS 模板引擎.pdf

    此模板引擎的浏览器兼容性良好,支持IE6+及Chrome等现代浏览器。为了体验Web端的Velocity语法解析,你可以访问提供的测试案例,但需要注意,这个案例可能需要高级浏览器支持。 Velocity.js的语法具有高容错性,其...

    thymeleaf:Thymeleaf是适用于Web和独立环境的现代服务器端Java模板引擎

    Thymeleaf是一款广泛应用于现代Web开发中的服务器端Java模板引擎,它以其强大的功能和易于理解的语法在开发者社区中赢得了高度评价。Thymeleaf的主要目标是提供一种优雅的方式来编写静态的HTML模板,这些模板在运行...

    现代风格的网页模板

    2. 平面设计:平面设计是现代网页模板的一个显著特征,它强调简洁、清晰的视觉元素,减少过度装饰,使网站看起来更现代且易于理解。 3. 强调色彩:现代网页模板通常使用大胆鲜明的颜色搭配,以突出品牌识别度和视觉...

    大气现代工业网站模板

    大气现代工业网站模板是一款专为工业建筑公司设计的HTML5模板,旨在提供一种高效、直观且具有视觉吸引力的在线展示方式。此模板以其大气、现代的设计风格,充分展现了工业领域的专业性和创新精神,有助于提升企业的...

    template-engine

    在IT行业中,模板引擎(Template Engine)是一种常用于自动化生成代码或动态网页内容的工具。...理解并熟练运用模板引擎是现代Web开发中的重要技能,对于提升项目质量和开发者的生产力具有显著作用。

    现代个人简历HTML模板

    2. **响应式设计**:确认模板具有响应式布局,能在各种屏幕尺寸上自动调整,提供一致的用户体验。 3. **易读性**:选择合适的字体大小和颜色对比,确保文字清晰可读,避免过多的装饰元素干扰信息的传达。 4. **专业...

    templatesrd:模板引擎的研发

    7. **与Java框架的集成**: 在Java Web开发中,模板引擎常与Spring MVC、Struts2等框架集成,通过这些框架提供的视图解析能力,可以轻松地将处理后的数据渲染成视图。例如,Thymeleaf和Spring Boot的结合,可以实现零...

    ThinkPHP使用smarty模板引擎的方法

    在现代Web开发中,模板引擎是用来将数据和逻辑分离的一个重要组成部分,它能够帮助开发者更便捷地进行视图层的开发。ThinkPHP作为一款流行的PHP开发框架,支持多种模板引擎,其中Smarty是一个广泛使用且功能强大的...

    inja:现代C ++的模板引擎

    Inja是现代C ++的模板引擎,其灵感来自 for python。 它具有简单而强大的模板语法,其中包含您需要的所有变量,循环,条件,包含,回调和注释,并根据需要进行嵌套和组合。 Inja使用nlohmann的精彩库进行数据输入。 ...

Global site tag (gtag.js) - Google Analytics