模闆技術在現代的軟體開發中有着重要的地位,而目前最流行的兩種模闆技術恐怕要算freemarker和velocity了,webwork2.2對兩者都有不錯的支援,也就是說在webwork2中你可以随意選擇使用freemarker或velocity作為view,模闆技術作為view的好處是很多,尤其和jsp比較起來優點更大,衆所周知jsp需要在第一次被執行的時候編譯成servlet,那麼這個過程是很慢的,當然很多應用伺服器都提供預編譯的功能,但是在開發的時候仍然給我們程式員帶來了很多痛苦,每次修改都要多幾秒鐘,那在一天的開發中就有很多時間浪費在jsp的編譯上了。用webwork
in action的作者的話來說:“每次修改之後重新運作都要等等幾秒是令人失望的,而頻繁地修改jsp更是會令你的失望情緒變本加厲“。我們把模闆技術引入到view中去可以帶來更好的開發效率,而且模闆的速度要比jsp快(雖然編譯過後的jsp在速度上已經滿足我的需求了,呵呵)。 當然模闆技術可以用在很多領域,可不隻在view那裡。我們可以通過模闆技術來生成xml,生成jsp,生成java檔案等等,說到這裡,大家通常會使用模闆技術用在公司的架構裡,這樣就可以很快速的生成添删改查的代碼,需要的隻是模闆,其他比如還有郵件模闆等等。
以上是模闆的作用,那麼現在開源的模闆技術有好幾種,多了之後就有一個選擇的問題了,如何選擇一個滿足自己需要的模闆的呢,寫了一個例子,我使用了幾種設計模式來完成了這個例子,這個例子中,同時使用了freemarker和velocity,這樣同學們可以通過代碼很直覺的比較兩種模闆技術,通過這個例子,我認識到freemarker在功能上要比velocity強大
1。在view層的時候,它提供了format日期和數字的功能,我想大家都有在頁面上format日期或數字的經驗,用jsp的同學可能對jstl的fmt标簽很有感情,使用了freemarker之後也可以使用freemarker提供的功能來formmat日期和資料,這個功能我想是很貼心的
2。通過我的使用我發現freemaker的eclipseplugin要比velocity的eclipseplugin好,好在很多地方呢,freemarker的插件除了支援freemarker文法也支援html語句,而velocity的插件貌似隻支援velocity的文法,html就隻是用普通的文本來顯示了,在這一點上freemarker占上風了
3。freemarker對jsptag的支援很好,算了,不到迫不得已還是不要這樣做吧。
還有就是兩者的文法格式,這一點上不同的人有不同傾向
下面就先介紹标簽吧
一、freemarker模闆檔案主要有4個部分組成
1、文本,直接輸出的部分
2、注釋,即<#--...-->格式不會輸出
3、插值(interpolation):即${..}或者#{..}格式的部分,将使用資料模型中的部分替代輸出
4、ftl指令:freemarker指令,和html标記類似,名字前加#予以區分,不會輸出。
ftl指令規則
freemarker有三種ftl标簽,這和html的标簽是完全類似的
開始标簽:<#directivename parameters>
結束标簽:</#directivename>
空标簽: <#directivename parameters />
實際上,使用标簽時前面的#符号也可能變成@,如果該指令是一個使用者指令而不是系統内建指令時,應将#符号改為@符号
插值規則
freemarker的插值有如下兩種類型
1、通用插值:${expr}
2、數字格式化插值:#{expr}或者#{expr;format}
通用插值,有可以分為四種情況
a、插值結果為字元串值:直接輸出表達式結果
b、插值結果為數字值:根據預設格式(#setting 指令設定)将表達式結果轉換成文本輸出。可以使用内建的字元串函數格式單個插值,例如
<#setting number_format = "currency" />
<#assign price = 42 />
${price}
${price?string}
${price?string.number}
${price?string.currency}
${price?string.percent}
c、輸出值為日期值:根據預設格式(由 #setting 指令設定)将表達式結果轉換成文本輸出,可以使用内建的字元串函數格式化單個插值,例如
<#assign lastupdated = "2009-01-07 15:05"?datetime("yyyy-mm-dd hh:mm") />
${lastupdated?string("yyyy-mm-dd hh:mm:ss zzzz")};
${lastupdated?string("eee,mmm d,yy")};
${lastupdated?string("eeee,mmmm dd,yyyy,hh:mm:ss a '('zzz')'")};
${lastupdated?string.short};
${lastupdated?string.long};
${lastupdated?string.full};
d、插值結果為布爾值
<#assign foo=true />
${foo?string("是foo","非foo")}
數字格式化插值
數字格式化插值可采用#{expr;format}的形式來格式化數字,其中format可以是:
mx:小數部分最小x位
mx:小數部分最大x位
例如:
<#assign x = 2.582 />
<#assign y =4 />
#{x;m2};
#{y;m2};
#{x;m1};
#{y;m1};
#{x;m1m2};
#{y:m1m2};
二、表達式
表達式是freemarker的核心功能。表達式放置在插值文法(${...})之中時,表面需要輸出表達式的值,表達式文法也可以與freemarker标簽結合,用于控制輸出
1、直接指定值
例如:
a、字元串
${'我的名字是\"yeek\"'};
${"我的檔案儲存在d:\\盤"};
b、數值
c、布爾值
d、日期型
freemarker支援date、time、datetime三種類型,這三種類型的值無法直接指定,通常需要借助字元串的date、time、datetime三個内建函數進行轉換才可以
<#assign test1 = "2009-01-22"?date("yyyy-mm-dd") />;
<#assign test2 ="16:34:43"?time("hh:mm:ss") />
<#assign test2 = "2009-01-22 17:23:45"?datetime("yyyy-mm-dd hh:mm:ss") />
${test1?string.full}
e、集合
集合以方括号包括,各集合元素之間以英文逗号(,)分隔,看如下的示例:
<#list["星期一",,["星期二",["星期三",["星期四",["星期五"] as x>
${s};
</#list>
f、map集合
map對象使用花括号包括,map中的key-value對之間以英文冒号(:)隔開,多組key-value對之間以英文逗号(,) 隔開
例如
<#assign score = {"國文":78,"數學":83,"java":89} >
<#list score?key as x>
${x}--->${score[x]};
</#list>
2、輸出變量值
freemarker的表達式輸出變量時,這些變量可以是頂層變量,也可以是map對象中的變量,還可以是集合中的變量,并可以使用點(.)文法來通路java對象的屬性
a、頂層變量
map root = new hashmap();
root.put("name","wenchao");
對應頂層變量,直接使用${variablename}來輸出變量值,變量名隻能是數字、字母、下劃線、$、@和#的組合,并不能以數字開頭
b、輸出集合元素
如果需要輸出集合元素,則可以根據集合元素的索引來輸出元素。集合元素的索引以方括号指定。
假設有集合對象為:["星期一","星期二","星期三","星期四","星期五","星期六"],該集合對象名為week, 如果需要輸出星期三,則可以使用如下文法:
${week[2]}
集合裡的第一個元素的索引是0
c、輸出map元素
這裡的map對象可以是直接hashmap的執行個體,甚至包括 javabean執行個體,對應javabean執行個體,我們一樣可以把其當成屬性為key,屬性為value的map執行個體
3、字元串操作
a、字元串連結
字元串連接配接有兩種文法
a、使用${..}(或#{..})在字元串常量部分插入表達式的值,進而完成字元串連接配接
b、直接使用連接配接運算符(+)來連接配接字元串
使用第一種文法來連接配接字元串
${"hello,${user}!"}
第二種使用連接配接符号來連接配接字元串
${"hello,"+user+"!"};
值的注意的是,${..}隻能用于文本部分,是以,下面的代碼是錯誤的:
<#if ${isbig}>wow!</#if>
<#if "${isbig}">wow!</#if>
應該寫成:
<#if isbig>wow!</#if>
b、截取字元串
map root = new hashmap();
root.put("book","瘋狂ajax講義");
${book[0]}
${book[4]}
${book[1..4]}
4、集合連接配接運算符
這裡所說的集合連接配接運算時将兩個集合連接配接成一個新的集合,連接配接集合的運算符是+,例如
<#list ["星期一"," 星期二","星期三"]+["星期四","星期五"] as x>
${x}
5、map連接配接運算符
map對象的連接配接運算也是将兩個map對象連接配接成一個新的map對象,map對象的連接配接運算符是+。如果兩個map對象具有相同的 key,則後加入map裡的key所
對應的value替代原來key所對應的value
6、算術運算符
freemarker表達式中完全支援算術運算,freemarker支援的算術運算符包括: +,-,*,/,%
看如下代碼示範
<#assign x = 5 />
${x* -100}
${x/2}
${12%10}
在表達式中使用算術運算時要注意以下幾點。
a、運算符兩邊的運算數必須是數字,是以下面的代碼是錯誤的:
${3*"5"}
b、使用+(既可以作為加号,也可以作為字元串連接配接運算符)運算時,如果一邊是數字,一邊是字元串,就會自動将數字轉化為字元串。例如
${3+"5"}
輸出結果:35
c、使用内建的int函數可對數值取整。例如
<#assign x = 5>
${(x/2)?int}
${1.1?int}
${1.999?int}
${-1.9999?int}
${-1.1?int}
7、比較運算符
表達式中支援的比較運算符有如下幾個
a、=(或者==):判斷兩個值是否相等.
b、!=:判斷兩個值是否不相等
c、 >(或者gt):判斷坐标值是否大于右邊值
d、 >=(或者gte):判斷坐标值是否大于等于右邊值
e、 <(或者lt):判斷左邊值是否小于右邊值
f、 <=(或者lte):判斷左邊值是否小于等于右邊值
8、邏輯運算符
邏輯運算符有如下幾個
a、邏輯與:&&
b、邏輯或:||
c、邏輯非:!
邏輯運算符隻能作用于布爾值,否則将産生錯誤。
9、内建函數
freemarker還提供了一些内建函數來轉換輸出,可以在任何變量後緊跟?,?後緊跟内建函數,就可通過内建函數來轉換輸出變量
下面是常用的内建的字元串函數
a、html:對字元串進行html編碼
b、cap_first:将字元串第一個字母成大寫
c、lower_case:将字元串轉換成小寫
d、upper_case:将字元串轉換成大寫
e、trim: 去掉字元串前後的空白字元
下面是集合的常用的内建函數
a、size: 獲得序列中元素的數目
下面是數字值的常用的内建函數
a、int 取得數字的整數部分
例如
<#assign test="tom & jerry" />
${test?html}
${test?upper_case?html}
10、空值處理運算符
freemarker對空值的處理非常嚴格,freemarker的變量必須有值,沒有被指派的變量就會抛出異常。
11、運算符優先級
三、freemarker 的常用指令
1、if指令
分支控制語句
文法格式如下
<#if condition>
....
<#elseif condition2>
...
<#elseif condition3>
<#else>
</#if>
2、switch、case、default、break指令
<#switch value>
<#case refvalue>
...
<#bread>
<#default>
</#switch>
雖然freemarker提供了switch指令,但它并不推薦使用switch指令來控制也輸出,而是推薦使用freemarker的if..elseif..else 指令來替代它。
3、list、break指令
list指令時一個典型的疊代輸出指令,用于疊代輸出資料模型中的集合。list指令的文法格式如下:
<#list sequence as item>
...
</#list>
除此之外,疊代集合對象時,還包括兩個特殊的循環變量:
a、item_index:目前變量的索引值。
b、item_has_next:是否存在下一個對象
也可以使用<#break>指令跳出疊代
<#list ["星期一","星期二","星期三","星期四","星期五"] as x>
${x_index +1}.${x} <#if x_has_next>,</#if>
<#if x = "星期四"><#break></#if>
</#list>
4、include 指令
include指令的作用類似于jsp的包含指令,用于包含指定頁,include指令的文法格式如下
<#include filename [options]
在上面的文法格式中,兩個參數的解釋如下
a、filename:該參數指定被包含的模闆檔案
b、options:該參數可以省略,指定包含時的選項,包含encoding和parse兩個選項,encoding指定包含頁面時所使用的解碼集,而parse指定被
包含是否作為ftl檔案來解析。如果省略了parse選項值,則該選項值預設是true
5、 import指令
該指令用于導入freemarker模闆中的所有變量,并将該變量放置在指定的map對象中,import 指令的文法格式如下
<#import path as mapobject>
在上面的文法格式中,path指定要被導入的模闆檔案,而mapobject是一個map對象名,通過這行代碼,将導緻path模闆中的所有變量都被放置
在mapobject中
<#import "/lib/common.ftl" as com>
6、noparse指令
noparse指令指定freemarker不處理該指令裡包含的内容,該指令的文法格式如下:
<#noparse>
...
</#noparse>
7、escape、noescape指令
8、assign指令
它用于為該模闆頁面建立或替換一個頂層變量
9、setting指令
該指令用于設定freemarker的運作環境,該指令的文法格式如下:
<#setting name = value>
name 的取值範圍包括如下幾個
locale :該選項指定該模闆所用的國家/語言選項
number_format:該選項指定格式化輸出數字的格式
boolean_format:該選項指定兩個布爾值的文法格式,預設值是"true、false"
date_format,time_format,datetime_format:該選項指定格式化輸出日期的格式
time_zone: 設定格式化輸出日期時所使用的時區
10、macro、nested、return指令
下面再介紹一個例子
java代碼
public class templatetest {
/**
* @param args
*/
public static void main(string[] args) throws exception{
/* 準備資料 */
map latest = new hashmap();
latest.put("url", "products/greenmouse.html");
latest.put("name", "green mouse");
map root = new hashmap();
root.put("user", "big joe");
root.put("latestproduct", latest);
root.put("number", new long(2222));
root.put("date",new date());
list listtest = new arraylist();
listtest.add("1");
listtest.add("2");
root.put("list",listtest);
templateengine freemarkerengine = (templateengine)templatefactory.getinstance().getbean("freemarker");
freemarkerengine.run(root);//使用freemarker模闆技術
templateengine velocityengine = (templateengine)templatefactory.getinstance().getbean("velocity");
velocityengine.run(root);//使用velocity模闆技術
}
}
工廠類,用來得到模闆引擎
public class templatefactory {
private static templatefactory instance;
private map objectmap;
static{
instance = new templatefactory();
public templatefactory() {
super();
this.objectmap = new hashmap();
synchronized (this) {
objectmap.put("freemarker", new freemarkertemplateengine(){
public string gettemplatepath() {
return "template";
}
});
objectmap.put("velocity", new velocitytemplateengine());
}
public static templatefactory getinstance(){
return instance;
* 模仿spring的工廠
* @param beanname
* @return
public object getbean(string beanname){
return objectmap.get(beanname);
引擎接口
public interface templateengine {
void run(map context)throws exception;
模闆引擎的實作使用method template模式,因為有兩個實作,這兩個實作又存在公共的邏輯,是以選擇了這個模式
public abstract class abstracttemplateengine implements templateengine{
public abstract string gettemplatepath();
public abstract string gettemplate();
public abstract string getenginetype();
public void run(map context)throws exception{
if(constants.engine_type_freemarker.equals(getenginetype()))
executefreemarker(context);
else
executevelocity(context);
private void executefreemarker(map context)throws exception{
configuration cfg = new configuration();
cfg.setdirectoryfortemplateloading(
new file(gettemplatepath()));
cfg.setobjectwrapper(new defaultobjectwrapper());
cfg.setcachestorage(new freemarker.cache.mrucachestorage(20, 250));
template temp = cfg.gettemplate(gettemplate());
writer out = new outputstreamwriter(system.out);
temp.process(context, out);
out.flush();
private void executevelocity(map root)throws exception{
velocity.init();
velocitycontext context = new velocitycontext(root);
org.apache.velocity.template template = null;
template = velocity.gettemplate(gettemplatepath()+gettemplate());
stringwriter sw = new stringwriter();
template.merge( context, sw );
system.out.print(sw.tostring());
這個是freemarker實作
public class freemarkertemplateengine extends abstracttemplateengine{
private static final string default_template = "freemarkerexample.ftl";
* 這個方法應該實作的是讀取配置檔案
public string gettemplatepath() {
return null;
public void run(map root) throws exception{
super.run(root);
public string gettemplate() {
// todo auto-generated method stub
return default_template;
public string getenginetype() {
return constants.engine_type_freemarker;
}
這個是velocity實作
public class velocitytemplateengine extends abstracttemplateengine{
private static final string default_template = "velocityexample.vm";
return "/template/";
public void run(map map) throws exception{
super.run(map);
return constants.engine_type_velocity;
以下是模闆
1,freemarker模闆
freemarker template test:
string test-----------${user}-----------${number}-----------${latestproduct.url}-----------${latestproduct.name}
condition test-----------
<#if user == "big joe">
list iterator-----------
<#list list as aa>
${aa}
</#list>
</#if>
date test-----------${date?string("mmm/dd/yyyy")}
2,velocity模闆
java代碼
******************************************************************************************************************
velocity template test:
#if ($user == "big joe")
#foreach( $aa in $list )
$aa
#end
date test-----------${date}
至此整個例子就結束了,這個例子比較直覺的表現兩種技術的應用