FreeMarker與JSP 2.0 + JSTL組合進行比較。
FreeMarker優點:
-
FreeMarker不受Servlet或網絡/ Web的限制; 它只是一個類庫通過將模板與Java對象(數據模型)合併來生成文本輸出。您可以隨時隨地執行模板; 沒有HTTP請求轉發或類似的技巧,根本不需要Servlet環境。因此,您可以輕鬆地將其集成到任何系統中。
-
更簡潔的語法 考慮這個JSP(假設
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
c:if test="${t}">
True
</c:if>
<c:choose>
<c:when test="${n == 123}">
Do this
</c:when>
<c:otherwise>
Do that
</c:otherwise>
</c:choose>
<c:forEach var="i" items="${ls}">
- ${i}
</c:forEach>
-
和等效的FTL:
<#if t>
True
</#if>
<#if n == 123>
Do this
<#else>
Do that
</#if>
<#list ls as i>
- ${i}
</#list>
-
在模板中沒有servlet特定的範圍和其他高度技術性的東西(除非你有意將它們暴露在數據模型中)。它是從一開始就爲MVC製作的,它只關注演示。
-
您可以從任何地方加載模板; 從類路徑,數據庫等
-
缺省情況下,區域設置敏感數字和日期格式。當您爲人類觀衆輸出時,您需要做的只是寫作,
${x}
而不是<fmt:formatNumber value="${x}" />
。 -
更容易定義特殊的宏和功能。
-
在地毯下沒有清掃錯誤。缺少變量和
null
-s不會默認默認爲0
/false
/ empty-string,但會導致錯誤。在這裏查看更多信息... -
“對象包裝”。這使您可以以定製的面向表示的方式將對象顯示給模板(例如,請參閱此處使用此技術的模板可以看到W3C DOM節點)。
-
宏和函數只是變量,所以它們可以像任何其他值一樣簡單地作爲參數值傳遞,放入數據模型等。
-
第一次(或更改後)訪問頁面時幾乎不明顯的延遲,因爲沒有昂貴的編譯發生。
FreeMarker缺點:
-
不是“標準”。工具和IDE集成較少,開發人員知之甚少,總體來說,行業支持少得多。(但是,大多數JSP標記庫可以在FreeMarker模板中使用正確的設置,除非它們基於
.tag
文件。) -
它的語法不符合HTML / XML規則,除了一些視覺相似性,這對於新用戶來說是混亂的(這是簡潔的代價)。JSP也不會跟隨它,但它更接近它。
-
由於宏和函數只是變量,因此只能在運行時檢測到不正確的指令和參數名稱以及缺少的所需參數。
-
不適用於JSF。(它可以在技術上工作,但沒有人實現了。)
如果您正在考慮在現有應用程序或僅支持JSP的遺留框架中使用FreeMarker替換JSP,則可以閱讀此文件:程序員指南/其他/使用FreeMarker與servlet /使用FreeMarker for“Model 2”
2.爲什麼FreeMarker對於null
-s和缺失變量這麼挑剔,怎麼辦?
要概述此條目是什麼:FreeMarker默認情況下會嘗試訪問不存在的變量或 null
值(這兩個與FreeMarker相同)作爲錯誤,它會中止模板執行。
首先,你應該明白挑剔的原因。大多數腳本語言和模板語言相當寬容,缺少變量(和 null
-s),它們通常將它們視爲空字符串和/或0和/或邏輯值。這個行爲有幾個問題:
-
它可能隱藏意外的錯誤,例如變量名稱中的錯字,或者模板作者引用程序員不會將該模板放入數據模型的變量,或程序員使用不同的名稱時。人類很容易犯這樣的錯誤,而電腦卻沒有,所以錯過這個機會,模板引擎可以顯示這些錯誤是一個糟糕的事情。即使您在開發過程中仔細檢查模板的輸出,也很容易查看錯誤 ,因爲您會默認不會打印警告,因爲您輸入了變量名稱(您注意到了嗎?)。還要考慮維護,當你稍後修改你的應用程序; 大概你不會重新檢查模板(許多應用程序有數百它們),每次仔細,所有可能的情況。單元測試通常不包括網頁內容非常好(如果你有...); 他們大多隻是檢查網頁中某些手動設置的模式,所以他們經常會使用實際上是錯誤的變化。但是如果頁面出現異常,這是人類測試人員會注意到的,單元測試會注意到(整個頁面都會失敗),在生產中,維護者會注意到(假設有人檢查錯誤日誌)。當你以後修改你的應用程序; 大概你不會重新檢查模板(許多應用程序有數百它們),每次仔細,所有可能的情況。單元測試通常不包括網頁內容非常好(如果你有...); 他們大多隻是檢查網頁中某些手動設置的模式,所以他們經常會使用實際上是錯誤的變化。但是如果頁面出現異常,這是人類測試人員會注意到的,單元測試會注意到(整個頁面都會失敗),在生產中,維護者會注意到(假設有人檢查錯誤日誌)。當你以後修改你的應用程序; 大概你不會重新檢查模板(許多應用程序有數百它們),每次仔細,所有可能的情況。單元測試通常不包括網頁內容非常好(如果你有...); 他們大多隻是檢查網頁中某些手動設置的模式,所以他們經常會使用實際上是錯誤的變化。但是如果頁面出現異常,這是人類測試人員會注意到的,單元測試會注意到(整個頁面都會失敗),在生產中,維護者會注意到(假設有人檢查錯誤日誌)。大概你不會重新檢查模板(許多應用程序有數百它們),每次仔細,所有可能的情況。單元測試通常不包括網頁內容非常好(如果你有...); 他們大多隻是檢查網頁中某些手動設置的模式,所以他們經常會使用實際上是錯誤的變化。但是如果頁面出現異常,這是人類測試人員會注意到的,單元測試會注意到(整個頁面都會失敗),在生產中,維護者會注意到(假設有人檢查錯誤日誌)。大概你不會重新檢查模板(許多應用程序有數百它們),每次仔細,所有可能的情況。單元測試通常不包括網頁內容非常好(如果你有...); 他們大多隻是檢查網頁中某些手動設置的模式,所以他們經常會使用實際上是錯誤的變化。但是如果頁面出現異常,這是人類測試人員會注意到的,單元測試會注意到(整個頁面都會失敗),在生產中,維護者會注意到(假設有人檢查錯誤日誌)。覆蓋網頁內容非常好(如果你有...); 他們大多隻是檢查網頁中某些手動設置的模式,所以他們經常會使用實際上是錯誤的變化。但是如果頁面出現異常,這是人類測試人員會注意到的,單元測試會注意到(整個頁面都會失敗),在生產中,維護者會注意到(假設有人檢查錯誤日誌)。覆蓋網頁內容非常好(如果你有...); 他們大多隻是檢查網頁中某些手動設置的模式,所以他們經常會使用實際上是錯誤的變化。但是如果頁面出現異常,這是人類測試人員會注意到的,單元測試會注意到(整個頁面都會失敗),在生產中,維護者會注意到(假設有人檢查錯誤日誌)。
<#if hasWarnigs>print warnings here...</#if>
-
做出危險的假設。腳本語言或模板引擎對應用程序領域一無所知,所以當它確定不知道爲0 / false的東西的價值時,這是一個相當不負責任和任意的事情。只是因爲不知道你現在的銀行餘額是多少,我們可以說是0美元?只是因爲不知道患者是否患有青黴素過敏,我們只能說他/她沒有嗎?只要考慮這些錯誤的含義。顯示錯誤頁面通常比顯示不正確的信息更好,導致用戶端的錯誤決定。
在這種情況下,不挑剔的是大部分地毯(不面對問題),當然大多數人覺得更方便,但是我們相信在大多數情況下,嚴格的會節省您的時間並提高您的軟件質量長跑。
另一方面,我們認識到,有些情況下,您不希望FreeMarker成爲好的理由,因此有解決方案:
-
您的數據模型包含
null
-s或具有可選變量通常是正常的 。在這種情況下使用這些操作符。如果您使用它們太頻繁,請嘗試重新考慮您的數據模型,因爲依賴它們太多不會使模板太冗長,但會增加隱藏錯誤和打印任意錯誤輸出的可能性(由於上述原因) 。 -
在某些應用程序中,您可能希望顯示不完整/損壞的頁面,而不是錯誤頁面。在這種情況下,您可以使用另一個錯誤處理程序。自定義錯誤處理程序可以跳過有問題的部分,或者在其中顯示錯誤指示器,而不是中止整個頁面呈現。但是請注意,雖然錯誤處理程序不會給變量提供任意的默認值,但是對於顯示關鍵信息的頁面來說,可能會更好地顯示錯誤頁面。
-
如果頁面包含不是非常重要的(像一些邊條)的部分,另一種功能,可能感興趣的是在
attempt
/recover
指令。
FreeMarker使用Java平臺的區域設置敏感數字格式化功能。您的區域設置的默認數字格式可能會使用分組或其他格式。如果您不想要,則必須使用number_format
FreeMarker設置覆蓋Java平臺建議的數字格式。例如:
cfg.setNumberFormat( “0 ######。”); //現在它將打印1000000 //其中cfg是freemarker.template.Configuration對象
但是請注意,人們經常發現難以讀取大數字而不分組分隔符。因此,一般來說,建議保留它們,如果數字是“計算機受衆”(在分組分隔符上混淆)的情況下,請使用c
內置的。例如:
<a href="/shop/productdetails?id=${ product.id?c }">詳細信息... </a>
對於計算機用戶,您需要?c
反正,因爲十進制分隔符也可以根據區域設置警惕。
不同的國家使用不同的十進制/分組分隔符號。如果您看到不正確的符號,那麼可能您的區域設置未正確設置。設置JVM的默認語言環境,或使用locale
FreeMarker設置覆蓋默認語言環境。例如:
cfg.setLocale(java.util.Locale.ITALY); //其中cfg是freemarker.template.Configuration對象
但是,有時你想輸出一個不是人類觀衆的數字,而對於“電腦觀衆”(像你想在CSS中打印一個大小),在這種情況下,你必須使用點作爲小數分隔符,而不管語言環境(語言)的頁面。爲了使用c
內置的,例如:
font-size:$ { fontSize?c } pt;
${aBoolean}
,以及如何解決?
與數字不同,布爾沒有普遍接受的格式,甚至不是同一頁面中的通用格式。喜歡當您在HTML頁面上顯示某個產品是否可以洗滌時,您幾乎不希望爲訪問者顯示“Washable:true”,而是“Washable:yes”。所以我們強迫模板作者(通過${washable}
導致錯誤)找出他的人類知識如何在給定的地方顯示布爾值。格式化一個布爾就像常見的方式${washable?string("yes",
"no")}
,${caching?string("Enabled", "Disabled")}
,${heating?string("on", "off")}
,等。
但是,有兩種情況是不切實際的:
-
當打印布爾值以生成計算機語言輸出,因此您想要
true
/false
,使用 。(這至少需要FreeMarker 2.3.20。在此之前,通常的做法是寫入 ,但是這是非常危險的,因爲它的輸出取決於當前的布爾格式設置,默認爲 / 。)${someBoolean?c}
${someBoolean?string}
"true"
"false"
-
當您以相同的方式格式化大多數布爾值。在這種情況下,您可以設置
boolean_format
setting(Configuration.setBooleanFormat
)來反映,然後自從FreeMarker 2.3.20就可以寫 。(注意,這不適用於 / 雖然 - 你必須在那裏使用。)${someBoolean}
true
false
?c
TemplateNotFoundException
或者FileNotFoundException
,“Template not found”錯誤信息)
首先,您應該知道FreeMarker不直接從文件系統路徑加載模板。相反,它使用一個簡單的虛擬文件系統,可以讀取非文件系統資源(jar內部的模板,數據庫表內的模板等)。該虛擬文件由配置設置決定Configuration.setTemplateLoader(TemplateLoader)
。即使TemplateLoader
您正在使用地圖到文件系統,它將有一個基本目錄,其中包含所有的模板,這將是虛擬文件系統的根目錄,您無法訪問(即絕對路徑將是仍然相對於虛擬文件系統根)。
提示解決問題:
-
如果您是配置FreeMarker的用戶,請確保設置正確
TemplateLoader
。 -
否則看看模板未找到錯誤的消息是否包含所
TemplateLoader
使用的描述 。如果沒有,您使用的是舊的FreeMarker版本,因此更新它。獲取FileNotFoundException
,而不是TemplateNotFoundException
也是一個跡象,所以你會得到錯誤信息幫助較小。(如果TemplateLoader
在錯誤消息中foo.SomeTemplateLoader@64f6106c
,因此沒有顯示一些相關參數,您可以要求作者定義更好toString()
。) -
一個常見的錯誤是使用一個
FileTemplateLoader
基於Servlet的Web應用程序,而不是一個WebappTemplateLoader
。它可能在一個環境中工作,但不能在另一個環境中工作,因爲Servlet規範不會對您的資源作爲普通文件提供許可,即使war
提取文件也不會。 -
知道當您從另一個模板中包含/導入模板時,如果您沒有啓動模板名稱
/
,它將相對於包含模板的目錄進行解釋。錯誤消息包含完整(已解析)的名稱,因此您應該注意到這一點。 -
檢查您是否使用
\
(反斜槓)而不是/
(斜槓)。(FreeMarker 2.3.22及更高版本將在錯誤消息中提醒您。) -
作爲最後的手段,打開類別的調試級別日誌記錄(在您正在使用的日誌框架中)
freemarker.cache
,以查看更多的發生。
您確定使用爲實際使用的FreeMarker版本編寫的文檔嗎?特別注意,我們的在線文檔是針對最新穩定的FreeMarker版本。你可以使用一個較舊的版本; 更新它。
您確定Java類加載器找到與freemarker.jar
您期望使用的相同 嗎?也許有一箇舊版本的freemarker.jar
周圍,陰影從來沒有。要查看此項,請嘗試使用模板打印版本號${.version}
。(如果它與“未知的內置變量:版本”錯誤消息死亡,那麼您使用非常非常舊的版本。)
如果您懷疑問題是您有多個 freemarker.jar
-s,典型的弊端是某些模塊具有與舊freemarker
組ID 相關的Maven或Ivy依賴關係 ,而不是更現代的org.freemarker
組ID。由於不同的組ID,這些不被Maven或Ivy視爲衝突的工件,因此兩個版本都會進入。在這種情況下,您必須排除freemarker
依賴關係。
如果您認爲文檔或FreeMarker出錯,請使用錯誤跟蹤器或郵件列表進行報告。謝謝!
8. FreeMarker標籤<
和>
我的編輯器或XML解析器混淆。該怎麼辦?
從FreeMarker的啓動2.3.4你可以用 [
和]
,而不是 <
和>
。更多詳情請閱讀...
FreeMarker的有關於變量名中使用的字符,也不會就變量名的長度沒有限制,但爲方便起見儘量選擇可以用簡單的變量引用表達式中使用的變量名(看到這裏)。如果你必須選擇一個更加極端的變量名,這不是一個大問題:見這裏。
10.如何使用包含減號(-
),冒號(:
),點(.
)或其他特殊字符的變量名稱(宏名稱,參數名稱)?
如果你有一個名爲“foo-bar”的變量,FreeMarker會誤會你的意思${foo-bar}
。在這種情況下,它會相信你想減去bar
from 的值 foo
。這個FAQ條目解釋瞭如何處理這樣的情況。
首先應該清楚的是,這些只是語法問題,否則FreeMarker對變量名中使用的字符也沒有限制,也沒有限制它們的長度。
如果特殊字符是減號(-
UCS 0x2D)或點(.
,UCS 0x2E)或冒號(:
UCS 0x3A)之一,則所有您需要做的是\
在這些字符之前放置一個反斜槓(),就像在foo\-bar
(自從FreeMarker
2.3.22)。那麼FreeMarker會知道你並不是用相同的符號表示運算符。這可以在您指定非引號標識符的地方,如宏和函數名稱,參數名稱以及所有類型的變量引用。(請注意,這些轉義僅適用於標識符,而不是字符串文字。)
當特殊字符不是負號,點或冒號時,會變得更加棘手。假設有問題的變量名是“a + b”。然後:
-
如果你想讀取變量:如果它是一個可變的東西,你可以寫
something["a+b"]
(記住,something.x
等同於something["x"])
。如果它是一個頂級變量,那些可以通過特殊的哈希變量來訪問.vars
,所以你可以寫.vars["a+b"]
,自然,這一招可與宏觀和函數調用太:<@.vars["a+b"]/>
,.vars["a+b"](1, 2)
。 -
如果你想創建或修改的變量:所有的指令,讓您創建或修改的變量(如
assign
,local
,global
,macro
,function
,等),使目標變量名的報價。例如,<#assign foo = 1>
與...相同<#assign "foo" = 1>
。所以,你可以寫東西喜歡<#assign "a+b" = 1>
和<#macro "a+b">
。 -
不幸的是,你不能用這樣的變量名(包含除其他特殊字符
-
,.
和:
)作爲宏參數名稱。
所有的拳頭,更新FreeMarker,因爲2.3.22和更高版本給出了一個更有用的錯誤信息,這幾乎可以解答這個問題。無論如何,原因如下。在JSP頁面上,引用所有參數(屬性)值,如果參數的類型是字符串或布爾值或數字,則不會這樣做。但是,由於自定義標籤可以在FTL模板中作爲純用戶定義的FTL僞指令訪問,因此必須在自定義標記中使用FTL語法規則,而不是JSP規則。因此,根據FTL規則,您不能引用布爾值和數值參數值,或者將它們解釋爲字符串值,
例如,flush
Struts Tiles insert
標籤的參數是布爾值。在JSP中,正確的語法是:
<tiles:insert page =“/ layout.jsp” flush =“true” /> ...
但在FTL你應該寫:
<@ tiles.insert page =“/ layout.ftl” flush = true /> ...
另外,出於類似的原因,這是錯誤的:
<tiles:insert page =“/ layout.jsp” flush =“$ {needFlushing}” /> ...
你應該寫:
<tiles:insert page =“/ layout.jsp” flush = needFlushing /> ...
(不flush=${needFlushing}
!)
jsp:include
?
不是<#include ...>
,因爲只包括另一個FreeMarker模板,而不涉及Servlet容器。
由於您要查找的包含方法是與Servlet相關的,而純FreeMarker並不知道Servlet甚至HTTP,所以Web應用程序框架決定是否可以執行此操作,如果是這樣。例如,在Struts 2中,您可以這樣做:
<@ s.include value =“/ WEB-INF / just-an-example.jspf”/>
如果基於Web應用程序框架的FreeMarker支持 freemarker.ext.servlet.FreemarkerServlet
,那麼您還可以執行此操作(自FreeMarker 2.3.15開始):
<@include_page path =“/ WEB-INF / just-an-example.jspf”/>
但是如果Web應用程序框架提供了自己的解決方案,那麼您可能更喜歡,畢竟它可能會做一些特別的事情。
13.如何將我的簡單Java方法/TemplateMethodModelEx
/ TemplateTransformModel
/TemplateDirectiveModel
實現的參數作爲plain java.lang.*
/ java.util.*
objects來獲取?
不幸的是,這個問題沒有簡單的通用解決方案。問題是FreeMarker的對象包裝非常靈活,當您從模板訪問變量時,這是很好的,但是在Java方面展開一個棘手的問題。例如,可以將非java.util.Map
對象包裝爲 TemplateHashModel
(FTL哈希變量)。但是,它不能解開java.util.Map
,因爲沒有包裹java.util.Map
。
那麼該怎麼辦呢?基本上有兩種情況:
-
爲演示目的而編寫的指令和方法(如幫助FreeMarker模板的“工具”)應將其參數聲明爲
TemplateModel
-s和更具體的子接口。畢竟,對象包裝是關於將數據模型轉換爲用於表示層的目的的,這些方法是表示層的一部分。如果你仍然需要一個普通的Java類型,你可以轉到ObjectWrapperAndUnwrapper
當前的 界面ObjectWrapper
(可以使用Environment.getObjectWrapper()
)。 -
不用於演示相關任務(但是對於業務邏輯等)的方法應該以純Java方法實現,並且根本不應該使用任何FreeMarker特定的類,因爲根據MVC範例,它們必須獨立於演示技術(FreeMarker )。如果從模板調用這種方法,那麼對象包裝器有責任 確保將參數轉換爲適當的類型。如果你使用
DefaultObjectWrapper
或BeansWrapper
那麼這將會自動發生。因爲DefaultObjectWrapper
,這種機制的效果要好得多,incompatibleImprovements
myMap[myKey]
表達式中使用非字符串鍵 ?現在該怎麼辦?
FreeMarker模板語言(FTL)的“哈希”類型與Java不同Map
。FTL的散列也是一個關聯數組,但是它也使用字符串鍵。這是因爲它是爲子變量引入的(如同password
, 與之user.password
相同user["password"]
),變量名稱是字符串。
如果你只需要列出的該鍵值對 Map
,你可以寫類似 <#list myMap as k, v>${k}: ${v}</#list>
(見更多的list
directive
在這裏)。這將枚舉Map
條目,並支持非字符串鍵。這需要FreeMarker 2.3.25或更高版本。(如果由於某種原因您無法升級到2.3.25,則可以使用相應的Java API Map
,如 <#list
myMap?api.entrySet() as kvp>${kvp.key}: ${kvp.value}</#list>
。)
如果您需要做的不僅僅是列表,那麼您將不得不轉而使用Java API Map
。你可以這樣說:myMap?api.get(nonStringKey)
。但是, ?api
要啓用,您可能需要配置FreeMarker一點(請參閱此處)。
請注意,由於Java Map
是關鍵字的確切類別,至少對於模板中計算的數字鍵,您必須將其轉換爲正確的Java類型,否則將不會找到該項。例如,如果您Integer
在地圖中使用密鑰,那麼您應該寫${myMap.get(numKey?int)}
。這是因爲FTL的故意簡化型系統只有一種數字類型,而Java區分了很多數值類型。請注意,當鍵值直接來自數據模型(即,您沒有使用模板中的算術計算修改其值)時,不需要轉換,包括方法返回值的情況,
?keys
/ ?values
,我得到了 java.util.Map
與真實映射條目混合的方法。當然,我只想得到地圖條目。
當然,你使用pure BeansWrapper
作爲對象的包裝器(而不是默認的 DefaultObjectWrapper
),或者它的一個自定義的子類,並且它的simpleMapWrapper
屬性是剩下的false
。不幸的是,這是默認的BeansWrapper
(爲了向後兼容),所以你必須明確地將它設置爲 true
實例化的位置。此外,至少自2.3.22以來,應用程序應該只是使用 DefaultObjectWrapper
(其incompatibleImprovements
設置至少爲2.3.22 -
如果您從純粹轉換BeansWrapper
,這一點尤爲重要),這從來沒有這個問題。
首先,您可能不想修改序列/散列,只需連接(添加)兩個或更多的,這將導致新的序列/散列,而不是修改現有的序列/散列。在這種情況下,使用序列連接和散列連接運算符。此外,您可以使用子序列運算符,而不是刪除序列項。但是,請注意性能影響:這些操作是快速的,但是這些操作的許多後續應用的結果的散列/序列(即,當您將操作的結果用作另一個操作的輸入時,以及等等)會慢慢閱讀。
現在,如果你仍然想修改序列/哈希值,然後閱讀...
FreeMarkes模板語言不支持修改序列/散列。它用於顯示已計算的東西,而不是用於計算數據。保持模板簡單。但不要放棄,你會看到一些建議和竅門。
最好的是如果您可以在數據模型構建程序和模板之間劃分工作,以使模板不需要修改序列/散列。也許如果你重新考慮你的數據模型,你會意識到這是可能的。但是,很少有一些情況需要修改序列/散列,以獲得一些複雜而純粹的表示相關算法。很少發生,所以考慮這個計算(或它的一部分)是否屬於數據模型域而不是表示域。我們假設你確定它屬於演示文稿領域。例如,您希望以非常聰明的方式顯示關鍵字索引,其算法需要您創建和編寫一些序列變量。
<#assign caculatedResults = 'com.example.foo.SmartKeywordIndexHelper'?新的()。計算(關鍵字)> <# - 這裏有一些簡單的算法,如: - > <UL> <#list caculatedResults as kw> <li> <a href="${kw.link}"> $ {kw.word} </a> </#列表> </ UL>
也就是說,將演示任務的複雜部分從模板移出到Java代碼中。請注意,它不影響數據模型,因此演示文稿仍然與其他應用程序邏輯分開。當然,缺點是模板作者需要Java程序員的幫助,但是對於可能需要的複雜算法,
現在,如果你仍然說你需要直接使用FreeMarker模板修改序列/哈希,這裏有一些解決方案,但是請仔細閱讀以下警告:
-
您可以使用內置
java.util.Map
的幫助 來訪問Java API 。您將需要從某個地方獲取(一個FTL哈希字面值不夠,因爲它只讀,也不支持)。例如,您可以公開一個Java方法或 返回的模板,這樣就可以 。api
myMap?api.put(11, "eleven")
Map
{}
api
TemplateMethodModelEx
new LinkeHashMap()
<#assign myMap = utils.newLinkedHashMap()>
-
您可以編寫
TemplateMethodModelEx
並TemplateDirectiveModel
可以修改某些類型的序列/散列的實現。只是某些類型,因爲TemplateSequenceModel
並TemplateHashModel
沒有修改的方法,所以你將需要序列或哈希來實現一些額外的方法。在FMPP中可以看到這個解決方案的一個例子。它允許你做這樣的事情(pp
存儲FMPP爲模板提供的服務):<#assign a = pp.newWritableSequence()> <@ pp.add seq = a value =“red”/>
該
pp.add
指令僅與使用的序列一起使用pp.newWritableSequence()
。因此,例如,模板作者無法使用此方式修改來自數據模型的序列。 -
如果您使用自定義的包裝器(因此可以編寫類似的東西
<@myList.append foo />
),序列可以有一些方法/指令 。
但是要注意的是,這些解決方案有一個問題:序列連接,序列切片 操作符(如seq[5..10]
)並且 ?reverse
不復制原始序列,僅僅包裝它(爲了效率),所以如果原始序列稍後改變,則所得到的序列將改變異常混疊效應)。哈希連接的結果存在同樣的問題; 它只是包裝了兩個散列,所以如果你修改了之前添加的哈希值,結果哈希將會神奇地改變。作爲一種解決方法,在執行上述問題的操作後,要麼確保不會修改用作輸入的對象,<#assign
b = pp.newWritableSequence(a[5..10])>
或創建結果的副本與由上述兩個點(例如在FMPP你可以做描述的溶液提供了一種方法和 <#assign c = pp.newWritableHash(hashA + hashB)>
)。當然,這很容易錯過,所以再次嘗試構建數據模型,因此您不需要修改集合,或者使用前面所示的演示任務幫助器類。
null
和FreeMarker模板語言?
FreeMarker模板語言根本不知道Java語言null
。它沒有 null
關鍵字,它不能測試是否有東西null
。當它在技術上面對a時 null
,它將其視爲一個缺失的變量。例如,如果x
是 null
在數據模型中,如果它不存在,${x!'missing'}
將打印“丟失”,你不能說出差異。另外,如果你想要測試Java方法是否已經返回 null
,只需要寫一些類似的東西 <#if
foo.bar()??>
。
您可能對此背後的理由感興趣。從表現層的觀點來看,null
和不存在的東西幾乎總是相同的。這兩者之間的區別通常只是一個技術細節,而不是應用邏輯的實現細節的結果。你不能比較一些東西null
(不像Java); null
在模板中比較某些東西是沒有意義的,因爲模板語言不會進行身份比較(比如Java ==
比較兩個對象時的Java 運算符),但是更常見的意義值比較(像Java的Object.equals(Object)
那樣)與null
任何一個工作 )。FreeMarker如何告訴某些具體的內容是否與缺少的東西相等呢?或者如果兩個丟失(未知)的東西是平等的?當然這些問題是無法回答的。
這種null
-unaware方法至少有一個問題 。當您從模板調用Java方法時,您可能希望將null
值作爲參數傳遞 (因爲該方法被設計爲在Java語言中使用,其中的概念 null
已知)。在這種情況下,您可以利用FreeMarker的錯誤(我們不會修復,直到我們爲null
方法傳遞值提供正確的解決方案):如果指定一個缺失的變量作爲參數,那麼它不會導致錯誤,但是null
將被傳遞給該方法。喜歡foo.bar(nullArg)
將調用bar
方法 null
作爲參數,假設沒有變量存在與“
使用assign
或local
指令將輸出捕獲到變量中 。例如:
<#assign takenOutput> <@ outputSomething /> </#assign> <@otherDirective someParam = captureOutput />
這是因爲您要打印的字符不能用輸出流使用的字符集(編碼)來表示,所以Java平臺(而不是FreeMarker)用問號替換有問題的字符。一般來說,您應該使用與模板相同的字符集(使用getEncoding()
模板對象的方法),甚至更安全,您應該始終對輸出使用UTF-8字符集。用於輸出流的字符集不是由FreeMarker決定的,而是由您創建Writer
傳遞給 process
模板方法的字符集 。
示例:這裏我在servlet中使用UTF-8字符集:
... resp.setContentType(“text / html; charset = utf-8”); Writer out = resp.getWriter(); ... t.process(root,out); ...
請注意,FreeMarker可能會生成問號(或其他替代字符),在這種情況下,上述顯然不會有幫助。例如,一個錯誤/錯誤配置的數據庫連接或JDBC驅動程序可能會帶有已經具有替換字符的文本。HTML表單是編碼問題的另一個潛在來源。最好在各個地方打印字符串字符的數字代碼,以查看問題出現在哪裏。
您可以在這裏閱讀更多關於charsets和FreeMarker的信息
20.如何在模板執行完成後檢索模板中計算的值?首先,確保您的應用程序設計良好:模板應顯示數據,幾乎不會計算數據。如果您仍然確定要這樣做,請閱讀...
當您使用時<#assign x = "foo">
,您實際上並沒有修改數據模型(因爲這是隻讀的,請參閱:程序員指南/雜項/多線程),但在處理x
的運行時環境中創建變量(參見Programmer's
Guide / Miscellaneous /變量,範圍)。問題是,當Template.process
返回時,這個運行時環境將被丟棄 ,因爲它是爲一次Template.process
調用創建的:
//內部將創建一個環境,然後丟棄 myTemplate.process(root,out);
爲了防止這種情況,您可以執行以下操作,與上述相同,只不過您有機會返回模板中創建的變量:
環境env = myTemplate.createProcessingEnvironment(root,out); env.process(); //處理模板 TemplateModel x = env.getVariable(“x”); // get variable x
#import
轉換)一個動態構造的變量名(比如將名稱存儲在另一個變量中)?
如果你真的不能避免這樣做(你應該是混淆的),你可以通過在一個字符串中動態構建適當的FTL源代碼,然後使用interpret
內置函數來解決這個問題。例如,如果要將其名稱存儲在varName
變量中的變量分配:
<@“<#assign $ {varName} ='example'>”?interpret />
一般來說,除非這些用戶是系統管理員或其他受信任的人員,否則您不應該允許。考慮模板作爲源代碼的一部分,就像 *.java
文件一樣。如果您仍然希望允許用戶上傳模板,請考慮以下幾點:
-
拒絕服務(DoS)攻擊:創建幾乎永遠運行(循環)或排出內存(通過連接到循環中的字符串)的模板是微不足道的。FreeMarker無法強制執行CPU或內存使用限制,因此這是FreeMarker級別沒有解決方案。
-
數據模型和包裝(
Configuration.setObjectWrapper
):數據模型可以訪問您放入數據模型的某些對象的公共Java API。默認情況下,不屬於這幫專門處理類型(的實例的對象String
,Number
,Boolean
,Date
,Map
,List
,陣列,和其他幾個人),其公開的Java API將被暴露。爲了避免這種情況,您必須構建數據模型,以使其僅暴露模板所必需的內容。爲了那個原因,SimpleObjectWrapper
您可能想使用(通過Configuration.setObjectWrapper
或object_wrapper
設置),然後純粹從Map
-s,List
-s,Array
-s,String
-s,Number
-s,Boolean
-s和Date
-s 創建數據模型。或者,您可以實現自己的極限制ObjectWrapper
,例如可以安全地暴露您的POJO。 -
Template-loader(
Configuration.setTemplateLoader
):模板可以按名稱(通過路徑)加載其他模板,如<#include "../secret.txt">
。爲了避免加載敏感數據,您必須使用TemplateLoader
雙重檢查文件加載是應該暴露的東西。FreeMarker嘗試防止在模板根目錄之外加載文件,而不管模板加載程序如何,但根據底層存儲機制,FreeMarker無法考慮使用漏洞(例如,~
跳轉到當前用戶的主目錄)。請注意freemarker.cache.FileTemplateLoader
檢查規範路徑,以便“*.ftl
-
該
new
內置(Configuration.setNewBuiltinClassResolver
,Environment.setNewBuiltinClassResolver
):它像模板中使用"com.example.SomeClass"?new()
,並且是針對在Java中部分地實現FTL庫很重要,但不應該在正常的模板是必要的。雖然new
不會實例化不是TemplateModel
-s的TemplateModel
類,FreeMarker包含一個 可以用於創建任意Java對象的類。其他“危險”TemplateModel
-s可以存在於您的類路徑中。另外,即使一個類沒有實現TemplateModel
,它的靜態初始化也將被運行。爲了避免這些,TemplateClassResolver
TemplateClassResolver.ALLOWS_NOTHING_RESOLVER
這是不可能的(還),但非常類似的東西是可能的,如果你寫一個實現類freemarker.template.TemplateMethodModelEx
或freemarker.template.TemplateDirectiveModel
分別,然後你在哪裏寫 或者 你寫 吧。請注意,由於函數(和方法)和宏只是FreeMarker中的簡單變量,因此使用此僞指令的指令。(出於同樣的原因,你也可以把或 實例到數據模型調用模板之前,或進入共享變量地圖(見 )當您初始化應用程序。)<#function
my ...>...</#function>
<#macro my ...>...</#macro>
<#assign
my = "your.package.YourClass "?
new
()>
assign
TemplateMethodModelEx
TemplateDirectiveModel
freemarker.template.Configuration.setSharedVariable(String,
TemplateModel)
首先,使用RETHROW_HANDLER
而不是默認DEBUG_HANDLER
(有關模板異常處理程序的更多信息,請閱讀...)。現在,當出現錯誤時,FreeMarker將不會向輸出打印任何內容,因此控件在您的手中。在您捕獲了 基本的異常之後, 您可以遵循兩種策略:Template.process(...)
-
打電話
httpResp.isCommitted()
,如果返回false
,那麼你打電話給httpResp.reset()
打印者打印一個“漂亮的錯誤頁面”。如果返回值爲true
,則嘗試完成頁面打印,以使訪問者清楚地看到頁面生成由於Web服務器上的錯誤而突然中斷。您可能需要打印大量冗餘HTML終點標記的和設置顏色和字體大小,以確保該錯誤消息將在瀏覽器窗口中實際可讀的(檢查的源代碼HTML_DEBUG_HANDLER
中src\freemarker\template\TemplateException.java
看到的示例)。 -
使用全頁緩衝。這意味着
Writer
不會將輸出逐漸發送給客戶端,而是將整個頁面緩存在內存中。由於您提供的 方法Writer
實例 ,這是您的責任,FreeMarker與它無關。例如,您可以使用a ,如果 通過拋出異常返回,則忽略該內容,併發送錯誤頁面,否則打印到輸出的內容 。用這種方法你肯定不會 不得不處理部分發送的頁面,但是它可能會對頁面的特性造成負面的影響(例如,用戶會慢慢生成長頁面的響應延遲,服務器也將消耗更多的RAM )。請注意,使用a 肯定不是最有效的解決方案,因爲它會隨着累積內容的增長而重新分配緩衝區。Template.process(...)
StringWriter
Template.process(...)
StringWriter
StringWriter
StringWriter
我們不會更改標準版本,因爲很多模板依賴於它。