freemarker模板解析過程
例如:一個freemarker表達式<body> ${hello} </body>,會被解析成三個部分,分別是
<body>
${hello}
</body>
前面和后面的body標簽,在freemarker中被定義為TextBlock,中間的變量定義為DollarVariable。那么目前的結構也就是RootExpression = TextBlock DollarVariable TextBlock。解釋器一進來將會對RootExpression進行解析,RootExpression將會依次調用TextBlock DollarVariable TextBlock進行解析。不同類型將會做不同操作,根據傳進來的Context參數進行相應賦值並輸出等。
當Template啟動解釋時,由Environment進入調用根元素的訪問動作,根元素會依次訪問所包含的TemplateElement,直到所有葉子節點訪問完成,這些訪問動作是通過調用Environment的visit方法控制,Environment做些相關必要操作,再根據訪問的節點類型調用相應節點的訪問操作。當訪問到包含需要解釋器的元素節點時,則會啟動解釋器做解釋操作,根據Expression類型,調用getStringValue,並傳入參數Environment,相應類型的表達式根據Environment解釋得到輸入字符串的值,返回並寫到響應流,即解釋完成。
freemarker內建函數介紹
Sequence的內置函數
1.sequence?first 返回sequence的第一個值。
2.sequence?last 返回sequence的最后一個值。
3.sequence?reverse 將sequence的現有順序反轉,即倒序排序
4.sequence?size 返回sequence的大小
5.sequence?sort 將sequence中的對象轉化為字符串后順序排序
6.sequence?sort_by(value) 按sequence中對象的屬性value進行排序
注意:Sequence不能為null
Hash的內置函數
1.hash?keys 返回hash里的所有key,返回結果為sequence
2.hash?values 返回hash里的所有value,返回結果為sequence
操作字符串內置函數
1.substring(start,end)從一個字符串中截取子串
start:截取子串開始的索引,start必須大於等於0,小於等於end
end: 截取子串的長度,end必須大於等於0,小於等於字符串長度,如果省略該參數,默認為字符串長度。
2.cap_first 將字符串中的第一個單詞的首字母變為大寫。
3.uncap_first將字符串中的第一個單詞的首字母變為小寫。
4.capitalize將字符串中的所有單詞的首字母變為大寫
5.date,time,datetime將字符串轉換為日期
注意:如果指定的字符串格式不正確將引發錯誤
6.ends_with 判斷某個字符串是否由某個子串結尾,返回布爾值
注意:布爾值必須轉換為字符串才能輸出
7.html 用於將字符串中的<、>、&和"替換為對應得<>":&
8.index_of(substring,start)在字符串中查找某個子串,返回找到子串的第一個字符的索引,如果沒有找到子串,則返回-1。
Start參數用於指定從字符串的那個索引處開始搜索,start為數字值。
如果start大於字符串長度,則start取值等於字符串長度,如果start小於0,則start取值為0。
9.length返回字符串的長度
10.lower_case將字符串轉為小寫
11.upper_case將字符串轉為大寫
12.contains 判斷字符中是否包含某個子串。返回布爾值
注意:布爾值必須轉換為字符串才能輸出
13.number將字符串轉換為數字
14.replace用於將字符串中的一部分從左到右替換為另外的字符串。
15.split使用指定的分隔符將一個字符串拆分為一組字符串
16.trim 刪除字符串首尾空格
操作數字內置函數
1.c 用於將數字轉換為字符串
2.string用於將數字轉換為字符串
Freemarker中預訂義了三種數字格式:number,currency(貨幣)和percent(百分比)其中number為默認的數字格式轉換
操作布爾值內置函數
string用於將布爾值轉換為字符串輸出
true轉為"true",false轉換為"false"
foo?string("yes","no")如果布爾值是true,那么返回"yes",否則返回no
freemarker日志實現過程分析
freemarker有自己的log類,這是一個抽象類,具體的日志打印委托給classpath里面合適的日志jar包來執行,尋找合適日志jar的查找順序是:Apache Log4J, Apache Avalon LogKit, JDK log。如果一個合適的日志實現類都沒有找到,日志功能將被抑制,並會使用System.err打印出錯誤提示信息。
如果我們想自己指定使用的日志類型,那么可以通過:
Loger.selectLoggerLibrary(int library);
注意:一定要在freemarker初始化階段進行設置,在調用任何freemarker api之前進行設置,否則freemarker將會與默認的日志實現進行綁定,從而自己指定的日志修改將不會起到作用。
Freemarker中大於號>的使用
在Freemarker中,比較數據的大小時候,要注意大於號(>)的使用。如果不注意,程序就會發生異常信息,如下面的例子:
|
1
2
3
4
|
<#assign x = 4>
<#if x>5 >
x >5
</#if>
|
以上的方式進行比較,就會發生異常,原因是Freemarker內部的解析處理原因,x>5中的大於號將會跟<#if中的小於號進行配對,導致解析出現問題。針對這種情況,有兩種方式解決:
方法一:加上括號。
|
1
2
3
4
|
<#assign x = 4>
<#if (x>5) >
x > 5
</#if>
|
方法二:使用gt符號。
|
1
2
3
4
|
<#assign x = 4>
<#if x gt 5 >
x > 5
</#if>
|
總結一下:
使用>=和>的時候有一點小問題。FreeMarker解釋>的時候可以把它當作FTL標簽的結束符。為了避免這種問題,不得不將表達式放到括號內:<#if (x > y) >,另外,可以使用lt代替<,lte代替<=,gt代替>,gte代替>=。由於歷史遺留的原因,FTL也支持\lt,\lte,\gt和\gte,使用他們和使用不帶反斜杠的效果一樣。
序列的重點知識小結
(1)序列的默認值為[],看下面的例子:
<#if (winnersList![])?size gt 0>
<table class="winner_table" border="0" cellspacing="0" cellpadding="0">
<tr>
<th class="bdr_gray">中獎賬號</th>
<th>猜測差值</th>
</tr>
<#list winnersList as list>
<tr>
<td class="bdr_gray">${list.accountId!""}</td>
<td>${list.deviation!""}</td>
</tr>
</#list>
</table>
</#if>
說明:在上面例子中,winnersList默認為[],它的內建函數為size
(2)序列的連接:
可以將兩個序列連接成一個新的序列,連接序列的運算符是'+',見下面的例子:
<#list ["一","二","三"] + ["四","五","六"] as x>
${x}
</#list>
輸出結果如下:
一二三四五六
(3)序列的切分:
舉個例子看序列的切分應用場景:有的時候我們在頁面中不需要顯示那么長的字符串,比如新聞標題,這樣用下面的例子就可以自定義顯示的長度
<#if title.content?length lt 8>
<a href>${title.content?default("")}</a>
<#else>
<a href title="${title.content}">${title.content[0..3]?default("")}</a>
</#if>
上面例子的作用是:如果這個字符串的長度小於8,那么就正常顯示,反之則取4位。
序列的切分要注意下面兩點:
從FreeMarker 2.3.3版本以后lastindex才能省略。
如果試圖訪問一個序列首變量之前的項或末變量之后的項將會引起錯誤,模板的執行也會中斷。
(4)子序列的定義:
序列中的項是表達式,那么也可以這樣做:[2 + 2, [1, 2, 3, 4], "what"],其中第一個子變量是數字4,第二個子變量是一個序列,第三個子變量是字符串"what"。
(5)數字序列的定義:
第一種定義序列的方式:
<#assign nums=[1,2,3,4,5,77,8,99]/>
使用list指令將序列輸出,
<#list nums as num>
${num}
</#list>
第二種定義序列的方式
定義了一個連續的序列,
<#assign nums=12..99/>
這種方式定義的序列的內容是12到99
說明:
從上面的例子可以看出,序列也可以用start..end定義存儲數字范圍的序列,這里的start和end是處理數字值表達式,比如2..5和[2, 3, 4, 5]是相同的,但是使用前者會更有效率(內存占用少而且速度快)。可以看出前者也沒有使用方括號,這樣也可以用來定義遞減的數字范圍,比如5..2。(此外,還可以省略end,只需5..即可,但這樣序列默認包含5,6,7,8等遞增量直到無窮大)。
(6)判斷序列是否包含某個元素
如果要判斷序列中是否包含某個指定的元素,可以使用序列的內建函數seq_contains。
注:seq_contains這個內建函數從FreeMarker 2.3.1 版本開始可用。而在2.3 版本中不存在。
<#--聲明一個序列,包含若干個元素-->
<#assign x = ["red", 16, "blue", "cyan"]>
<#--使用seq_contains判斷序列中的元素是否存在-->
"blue": ${x?seq_contains("blue")?string("yes", "no")}
"yellow": ${x?seq_contains("yellow")?string("yes", "no")}
16: ${x?seq_contains(16)?string("yes", "no")}
"16": ${x?seq_contains("16")?string("yes", "no")}
輸出結果:
"blue": yes
"yellow": no
16: yes
"16": no
附:seq_前綴在這個內建函數中是需要的,用來和contains 區分開。contains函數用來在字符串中查找子串(因為變量可以同時當作字符串和序列)。
StringTemplateLoader的用法
作為一個模板框架,freemarker的功能還是很強大的。在模板處理方面,freemarker有多種形式,最常見的方式是將模板文件放在一個統一的文件夾下面,如下形式:
Configuration cfg = new Configuration();
cfg.setDirectoryForTemplateLoading(new File("templates"));
如果我想把模板存放到數據庫中,可以實現嗎?答案是肯定的。在這里可以使用StringTemplateLoader來加載模板內容。主要的代碼實現如下所示:
Configuration cfg = new Configuration();
StringTemplateLoader stringLoader = new StringTemplateLoader();
String templateContent="hello ${name}!";
stringLoader.putTemplate("myTemplate",templateContent);
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate("myTemplate","utf-8");
freemarker報錯的處理方案
freemarker文件如果出錯,網站的前台頁面會報出很明顯的錯誤-焦黃的背景,血紅的文字,很不利於用戶體驗的。如何修改這個問題呢?
首先需要在struts.xml配置文件里添加下面一行代碼:
|
1
|
<constant name="struts.freemarker.manager.classname" value="net.swiftlet.freemarker.MyFreemarkerManager" />
|
接着新建MyFreemarkerManager類,如下所示:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class MyFreemarkerManager extends org.apache.struts2.views.freemarker.FreemarkerManager
{
private static final Logger LOG = LoggerFactory.getLogger(MyFreemarkerManager.class);
public void init(ServletContext servletContext) throws TemplateException
{
config = createConfiguration(servletContext);
config.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
contentType = DEFAULT_CONTENT_TYPE;
wrapper = createObjectWrapper(servletContext);
if (LOG.isDebugEnabled())
{
LOG.debug("Using object wrapper of class " + wrapper.getClass().getName());
}
config.setObjectWrapper(wrapper);
templatePath = servletContext.getInitParameter(INITPARAM_TEMPLATE_PATH);
if (templatePath == null)
{
templatePath = servletContext.getInitParameter("templatePath");
}
configureTemplateLoader(createTemplateLoader(servletContext, templatePath));
loadSettings(servletContext);
}
}
|
