freemarker程序开发
1、程序開發入門
1.1 創建配置實例
首先,你應該創建一個freemarker.template.Configuration的實例,然后調整它的設置。Configuration實例是存儲FreeMarker應用級設置的核心部分。同時,它也處理創建和緩存預解析模板的工作。也許你只在應用(可能是servlet)生命周期的開始執行它一次:
Configuration cfg = new Configuration();
// 指定模板文件從何處加載的數據源,這里設置成一個文件目錄。
cfg.setDirectoryForTemplateLoading(
new File("/where/you/store/templates"));
// 指定模板如何檢索數據模型,這是一個高級的主題了…
// 但先可以這么來用:
cfg.setObjectWrapper(new DefaultObjectWrapper());
從現在開始,應該使用單實例配置。要注意不管一個系統有多少獨立的組件來使用FreeMarker,它們都會使用他們自己私有的Configuration實例。
1.2 創建數據模型
在簡單的示例中你可以使用Java.lang和java.util包下的類,還有用戶自定義的Java Bean來構建數據對象。
? ? ? 使用java.lang.String來構建字符串。
? ? ? 使用java.lang.Number來派生數字類型。
? ? ? 使用java.lang.Boolean來構建布爾值。
? ? ? 使用java.util.List或Java數組來構建序列。
? ? ? 使用java.util.Map來構建哈希表。
? ? ? 使用你自己定義的bean類來構建哈希表,bean中的項和bean的屬性對應。例如product中的price屬性可以用product.price來獲取
構建數據模型的java代碼
// 創建根哈希表
Map root = new HashMap();
// 在根中放入字符串"user"
root.put("user", "Big Joe");
// 為"latestProduct"創建哈希表
Map latest = new HashMap();
// 將它添加到根哈希表中
root.put("latestProduct", latest);
// 在latest中放置"url"和"name"
latest.put("url", "products/greenmouse.html");
latest.put("name", "green mouse");
對于latestProduct你也可以使用有url和name屬性的Java Bean(也就是說,對象要有公共的String getURL()和String getName()方法);它和模板的觀點相同。
1.3 獲得模板
模板代表了freemarker.template.Template的實例。典型的做法是從Configuration實例中獲取一個Template實例。無論什么時候你需要一個模板實例,都可以使用它的getTemplate方法來獲取。在之前設置的目錄中,用test.ftl存儲示例模板,那么就可以這樣來做:
Template temp = cfg.getTemplate("test.ftl");
當調用這個方法的時候,將會創建一個test.ftl的Template實例,通過文件讀取,然后解析(編譯)它。Template實例以解析后的形式存儲模板,而不是以源文件的文本形式。
Configuration緩存Template實例,當再次獲得test.ftl時,它可能不會創建新的Template實例(因此不會讀取和解析文件),而是返回第一次創建的實例。
1.4 合并模板和數據模型
我們都已經知道的,數據模型+模板=輸出,我們已經有了一個數據模型(root)和一個模板(temp)了,所以為了得到輸出就需要合并它們。這是由模板的process方法完成的。它用數據模型的根和Writer對象作為參數,然后向Writer對象寫入產生的內容。為簡單起見,這里我們只做標準的輸出:
Writer out = new OutputStreamWriter(System.out);
temp.process(root, out);
out.flush();
一旦獲得了Template實例,就能將它和不同的數據模型進行不限次數(Template實例是無狀態的)的合并。此外,當Template實例創建之后test.ftl文件才能訪問,而不是調用處理方法時。
2、?數據模型
現在,我們已經知道如何使用基本的Java類(Map,String等)構建一個數據模型了。在內部,模板中可用的變量都是實現了freemarker.template.TemplateModel接口的Java對象。但在你自己的數據模型中,可以使用基本的Java集合類作為變量,因為這些變量會在內部被替換為適當的TemplateModel類型。這種功能特性被稱作是object wrapping對象包裝。對象包裝功能可以透明地把任何類型的對象轉換為實現了TemplateModel接口類型的實例。這就使得下面的轉換成為可能,如在模板中把java.sql.ResultSet轉換為序列變量,把javax.servlet.ServletRequest對象轉換成包含請求屬性的哈希表變量,甚至可以遍歷XML文檔作為FTL變量。包裝(轉換)這些對象,需要使用合適的,也就是所謂的對象包裝器實現(可能是自定義的實現);這將在后面討論。現在的要點是想從模板訪問任何對象,它們早晚都要轉換為實現了TemplateModel接口的對象。那么首先你應該熟悉來寫TemplateModel接口的實現類。
有一個freemarker.template.TemplateModel粗略的子接口對應每種基本變量類型(TemplateHashModel對應哈希表,TemplateSequenceModel對應序列,TemplateNumberModel對應數字等等)。例如,想為模板使用java.sql.ResultSet變量作為一個序列,那么就需要編寫一個TemplateSequenceModel的實現類,這個類要能夠讀取java.sql.ResultSet中的內容。我們常這么說,你使用TemplateModel的實現類包裝了java.sql.ResultSet,基本上只是封裝java.sql.ResultSet,來提供使用普通的TemplateSequenceModel接口訪問它。要注意一個類可以實現多個TemplateModel接口,這就是為什么FTL變量可以有多種類型
注意這些接口的一個細小的實現是和freemarker.template包一起提供的。例如,將一個String轉換成FTL的字符串變量,可以使用SimpleScalar,將java.util.Map轉換成FTL的哈希表變量,可以使用SimpleHash等等。
如果想嘗試自己的TemplateModel實現,一個簡單的方式是創建它的實例,然后將這個實例放入數據模型中(也就是把它放在哈希表的根上)。對象包裝器將會給模板提供它的原狀,因為它已經實現了TemplateModel接口,所以沒有轉換(包裝)的需要。(這個技巧當你不想用對象包裝器來包裝(轉換)某些對象時仍然有用。
(1)標量
有4種類型的標量:
? ?? 布爾值
? ?? 數字
? ?? 字符串
? ?? 日期
每一種標量類型都是TemplateTypeModel接口的實現,這里的Type就是類型的名稱。這些接口只定義了一個方法type getAsType();它返回變量的Java類型(boolean,Number,String和Date各自代表的值)的值。
注意:由于歷史遺留的原因,字符串標量的接口是TemplateScalarModel,而不是TemplateStringModel。
這些接口的一個細小的實現和SimpleType類名在freemarker.template包中是可用的。但是卻沒有SimpleBooleanModel類型;為了代表布爾值,可以使用TemplateBooleanModel.TRUE和TemplateBooleanModel.FALSE來單獨使用。
注意:由于歷史遺留的原因,字符串標量的實現類是SimpleScalar,而不是SimpleString。
在FTL中標量是一成不變的。當在模板中設置變量的值時,使用其他的實例來替換TemplateTypeModel實例時,是不用改變原來實例中存儲的值的。
(2)數據類型的難點
數據類型還有一些復雜,因為Java API通常不區別java.util.Date,只存儲日期部分(April 4, 2003),時間部分(10:19:18 PM),或兩者都存(April 4, 2003 10:19:18 PM)。為了用文本正確顯示一個日期變量,FreeMarker必須知道java.util.Date的哪個部分存儲了有意義上的信息,哪部分沒有被使用。不幸的是,Java API在這里明確的說,由數據庫控制(SQL),因為數據庫通常有分離的日期,時間和時間戳(又叫做日期-時間)類型,java.sql有3個對應的java.util.Date子類和它們相匹配。
TemplateDateModel接口有兩個方法:分別是java.util.Date getAsDate()和int getDateType()。這個接口典型的實現是存儲一個java.util.Date對象,加上一個整數來辨別“數據庫存儲的類型”。這個整數的值也必須是TemplateDateModel接口中的常量之一:DATE,TIME,DATETIME和UNKNOWN。
什么是UNKNOWN呢?我們之前說過,java.lang和java.util下的類通常被自動轉換成TemplateModel的實現類,就是所謂的對象包裝器。當對象轉換器面對一個java.util.Date對象時,而不是java.sql日期類的實例,它就不能確定“數據庫存儲的類型”是什么,所以就使用UNKNOWN。往后執行,如果模板需要使用這個變量,操作也需要使用“數據存儲的類型”,那就會停止執行并拋出錯誤。為了避免這種情況的發生,對于那些可能有問題的變量,模板開發人員需要幫助FreeMarker決定“數據庫存儲的類型”,使用內建函數date,time或datetime就可以解決了。注意一下,如果對要格式化參數使用內建函數string,比如foo?string("MM/dd/yyyy"),那么FreeMarker就不必知道“數據庫存儲的類型”了。
(3)容器
容器包括哈希表,序列和集合三種類型。
哈希表:
FreeMarker中的哈希表是實現了TemplateHashModel接口的Java對象。TemplateHashModel接口有兩個方法:TemplateModel get(String key),這個方法根據給定的名稱返回子變量,boolean isEmpty()這個方法表明哈希表是否含有子變量。get方法當在給定的名稱沒有找到子變量時返回null。
TemplateHashModelEx接口擴展了TemplateHashModel接口。它增加了更多的方法,使得可以使用內建函數values和keys來枚舉哈希表中的子變量。
經常使用的實現類是SimpleHash,該類實現了TemplateHashModelEx接口。從內部來說,它使用一個java.util.Hash類型的對象存儲子變量。SimpleHash類的方法可以添加和移除子變量。這些方法應該用來在變量被創建之后直接初始化。
在FTL中,容器是一成不變的。那就是說你不能添加,替換和移除容器中的子變量。
序列:
序列是實現了TemplateSequenceModel接口的Java對象。它包含兩個方法:TemplateModel get(int index)和int size()。
經常使用的實現類是SimpleSequence,該類內部使用一個java.util.List類型的對象存儲它的子變量。SimpleSequence有添加子元素的方法。在序列創建之后應該使用這些方法來填充序列。
集合:
集合是實現了TemplateCollectionModel接口的Java對象。這個接口只定義了一個方法:TemplateModelIterator iterator()。TemplateModelIterator接口和java.util.Iterator相似,但是它返回TemplateModel而不是Object,而且它能拋出TemplateModelException異常。
通常使用的實現類是SimpleCollection。
方法:
方法變量在存于實現了TemplateMethodModel接口的模板中。這個接口僅包含一個方法:TemplateModel exec(java.util.List arguments)。當使用方法調用表達式調用方法時,exec方法將會被調用。形參將會包含FTL方法調用形參的值。exec方法的返回值給出了FTL方法調用表達式的返回值。
TemplateMethodModelEx接口擴展了TemplateMethodModel接口。它沒有任何新增的方法。事實上這個對象實現這個標記接口暗示給FTL引擎,形式參數應該直接以TemplateModel形式放進java.util.List。否則將會以String形式放入List。
一個很明顯的原因是這些接口沒有默認的實現。
例如這個方法,返回第一個字符串在第二個字符串第一次出現時的索引位置,如果第二個字符串中不包含第一個字符串,則返回“-1”:
?
[java]?view plaincopy package com.cdtax.freemarker; import java.util.List; import freemarker.template.SimpleNumber; import freemarker.template.TemplateMethodModel; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; public class IndexOfMethod implements TemplateMethodModel { @Override public TemplateModel exec(List arg0) throws TemplateModelException { if(arg0.size() != 2) { throw new TemplateModelException("Wrong argments !"); } return new SimpleNumber(((String)arg0.get(1)).indexOf((String)arg0.get(0))); } }
test.ftl:
?
?
[html]?view plaincopy <html> <head> <title>welcome!</title> </head> <body> <h1>welcome ${user} !</h1> <p>our latest products: <a href="${latestproduct.url}">${latestproduct.name}</a>! <#macro repeat count> <#list 1..count as x> <#nested x, x/2, x==count> </#list> </#macro> <@repeat count=4 ; c, halfc, last> ${c}. ${halfc}<#if last> Last!</#if> </@repeat> <#macro do_thrice> <#nested 1> <#nested 2> <#nested 3> </#macro> <@do_thrice ; x> <#-- 用戶自定義指令 使用";"代替"as" --> ${x} Anything. </@do_thrice> <#import "lib/my_test.ftl" as my> <@my.copyright date="2014-04-01" /> ${my.mail} <#assign x = "something"> ${indexOf("met",x)} ${indexOf("foo",x)}??
test1.java:
?
?
[java]?view plaincopy package com.cdtax.freemarker; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; public class Test1 { public static void main(String[] args) throws Exception { //Configuration實例是存儲FreeMarker應用級設置的核心部分 Configuration cfg = new Configuration(); // 指定模板文件從何處加載的數據源,這里設置成一個文件目錄。 File path1 = new File(System.getProperty("user.dir")+"/src/ceshi"); cfg.setDirectoryForTemplateLoading(path1); // 指定模板如何檢索數據模型,這是一個高級的主題了… // 但先可以這么來用: cfg.setObjectWrapper(new DefaultObjectWrapper()); //獲得模板 Template temp = cfg.getTemplate("test.ftl"); // 創建根哈希表 Map root = new HashMap(); // 在根中放入字符串"user" root.put("user", "xiaoming"); // 為"latestProduct"創建哈希表 Map latest = new HashMap(); // 將它添加到根哈希表中 root.put("latestproduct", latest); // 在latest中放置"url"和"name" latest.put("url", "products/greenmouse.html"); latest.put("name", "green mouse"); root.put("indexOf", new IndexOfMethod()); Writer out = new OutputStreamWriter(System.out); // 將模板和數據模型合并 并輸出 temp.process(root, out); out.flush(); } }
最終運行結果:
?
<html>
<head>
<title>welcome!</title>
</head>
<body>
<h1>welcome xiaoming !</h1>
<p>our latest products:
<a href="products/greenmouse.html">green mouse</a>!
1. 0.5
2. 1
3. 1.5
4. 2 Last!
?
1 Anything.
?
2 Anything.
?
3 Anything.
<p>Copyright (C) 2014-04-01 Julia Smith. All rights reserved.</p>
jsmith@acme.com?
2
-1
如果需要訪問FTL運行時環境(讀/寫變量,獲取本地信息等),則可以使用Environment.getCurrentEnvironment()來獲取。
指令:
Java程序員可以使用TemplateDirectiveModel接口在Java代碼中實現自定義指令。詳情可以參加API文檔。
注意:
TemplateDirectiveModel在FreeMarker 2.3.11版本時才加入。用來代替快被廢棄的TemplateTransformModel。
實現一個指令,這個指令可以將在它開始標簽和結束標簽之內的字符都轉換為大寫形式。就像這個模板:
?
[html]?view plaincopy foo <@upper> bar <#-- 這里允許使用所有的FTL -->[html]?view plaincopy <#list ["red", "green", "blue"] as color> ${color} </#list> baaz </@upper> wombat
輸出:
?
foo
? ?BAR
? ? ? RED
? ? ? GREEN
? ? ? BLUE
? ?BAAZ
wombat
實現的java代碼:
?
[java]?view plaincopy package com.cdtax.freemarker; import java.io.IOException; import java.io.Writer; import java.util.Map; import freemarker.core.Environment; import freemarker.template.TemplateDirectiveBody; import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; public class UpperDirective implements TemplateDirectiveModel { @Override public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException { //檢查參數是否傳入 if(!params.isEmpty()) { throw new TemplateModelException("this directive doesn't allow parameters."); } if(loopVars.length != 0) { throw new TemplateModelException("this directive doesn't allow loop variable."); } //是否有非空的嵌套內容 if(body != null) { //執行嵌入體部分,和FTL中的<#nested>一樣,除了我們使用自己的writer來替代當前的output writer body.render(new UpperCaseFilterWriter(env.getOut())); } else { throw new RuntimeException("missing body"); } } /** * {@link Writer}改變字符流到大寫形式, * 而且把它發送到另一個{@link Writer}中 */ private static class UpperCaseFilterWriter extends Writer { private final Writer out; public UpperCaseFilterWriter(Writer out) { this.out = out; } public void write(char[] cbuf,int off,int len) throws IOException { char[] transformedCbuf = new char[len]; for(int i = 0; i < len; i++) { transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]); } out.write(transformedCbuf); } public void flush() throws IOException { out.flush(); } public void close() throws IOException { out.close(); } } }
現在我們需要創建這個類的實例,然后讓這個指令在模板中可以通過名稱“upper”來訪問(或者是其它我們想用的名字)。一個可行的方案是把這個指令放到數據模型中:
?
root.put("upper", new com.example.UpperDirective());
但更好的做法是將常用的指令作為共享變量放到Configuration中。
當然也可以使用內建函數new將指令放到一個FTL庫(宏的集,就像在模板中,使用include或import)中。
<#-- 也許在FTL中你已經有了實現了的指令 -->
<#macro something>
...
</#macro>
<#-- 現在你不能使用<#macro upper>,但是你可以使用: -->
<#assign upper = "com.example.UpperDirective"?new()>?
第二個示例,我們來創建一個指令,這個指令可以一次又一次地執行其中的嵌套內容,這個次數由指定的數字來確定(就像list指令),可以使用<hr>將輸出的重復內容分開。這個指令我們命名為“repeat”。示例模板如下:
[html]?view plaincopy <#assign x = 1> <@repeat count=4> Test ${x} <#assign x = x + 1> </@repeat> <@repeat count=3 hr=true> Test </@repeat> <@repeat count=3; cnt> ${cnt}. Test </@repeat>?
輸出為:
?
Test 1
Test 2
Test 3
Test 4
Test
<hr> Test
<hr> Test
1. Test
2. Test
3. Test
指令的實現類為:
[java]?view plaincopy package com.cdtax.freemarker; import java.io.IOException; import java.io.Writer; import java.util.Iterator; import java.util.Map; import freemarker.core.Environment; import freemarker.template.SimpleNumber; import freemarker.template.TemplateBooleanModel; import freemarker.template.TemplateDirectiveBody; import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; import freemarker.template.TemplateNumberModel; public class RepeatDirective implements TemplateDirectiveModel { private static final String PARAM_NAME_COUNT = "count"; private static final String PARAM_NAME_HR = "hr"; @Override public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException { //處理參數: int countParam = 0; boolean countParamSet = false; boolean hrParam = false; Iterator paramIter = params.entrySet().iterator(); while(paramIter.hasNext()) { Map.Entry ent = (Map.Entry)paramIter.next(); String paramName = (String)ent.getKey(); TemplateModel paramValue = (TemplateModel)ent.getValue(); if(paramName.equals(PARAM_NAME_COUNT)) { if(!(paramValue instanceof TemplateNumberModel)) { throw new TemplateModelException("The \"" + PARAM_NAME_HR + "\" parameter " + "must be a number."); } countParam = ((TemplateNumberModel)paramValue).getAsNumber().intValue(); countParamSet = true; if (countParam < 0) { throw new TemplateModelException("The \"" + PARAM_NAME_HR + "\" parameter " + "can't be negative."); } } else if(paramName.equals(PARAM_NAME_HR)) { if (!(paramValue instanceof TemplateBooleanModel)) { throw new TemplateModelException("The \"" + PARAM_NAME_HR + "\" parameter " + "must be a boolean."); } hrParam = ((TemplateBooleanModel)paramValue).getAsBoolean(); } else { throw new TemplateModelException("Unsupported parameter: " + paramName); } } if(!countParamSet) { throw new TemplateModelException("The required \"" + PARAM_NAME_COUNT + "\" paramter" + "is missing."); } if(loopVars.length > 1) { throw new TemplateModelException("At most one loop variable is allowed."); } //執行真正指令的執行部分: Writer out = env.getOut(); if(body != null) { for(int i = 0; i < countParam; i++) { //如果“hr”參數為真,那么就在所有重復部分之間打印<hr>: if (hrParam && i != 0) { out.write("<hr>"); } //如果有循環變量,那么就設置它: if(loopVars.length > 0) { loopVars[0] = new SimpleNumber(i + 1); } //執行嵌入體部分(和FTL中的<nested>一樣)。 //這種情況下,我們不提供一個特殊的writer作為參數: body.render(env.getOut()); } } } }
在test1.java中增加:root.put("repeat", new RepeatDirective());
?
節點變量:
節點變量體現了樹形結構中的節點。節點變量的引入是為了幫助用戶在數據模型中處理XML文檔,但是它們也可以用于構建樹狀模型,節點變量有下列屬性,它們都由TemplateNodeModel接口的方法提供。
? 基本屬性:
? ? ? TemplateSequenceModel getChildNodes():一個節點的子節點序列(除非這個節點是葉子節點,這時方法返回一個空序列或者是null)。子節點本身應該也是節點變量。
? ? ? TemplateNodeModel getParentNode():一個節點只有一個父節點(除非這個節點是節點樹的根節點,這時方法返回null)。
? 可選屬性。如果一個屬性在具體的使用中沒有意義,那對應的方法應該返回null:
? ? ? String getNodeName():節點名稱也是宏的名稱,當使用recurse和visit指令時,它用來控制節點。因此,如果想通過節點使用這些指令,那么節點的名稱是必須的。
? ? ? String getNodeType():在XML技術中:"element","text","comment"等類型。如果這些信息可用,就是通過recurse和visit指令來查找節點的默認處理宏。而且,它對其他有具體用途的應用程序也是有用的。
? ? ? String getNamespaceURI():這個節點所屬的命名空間(和用于庫的FTL命名空間無關)。例如,在XML中,這就是元素和屬性所屬XML命名空間的URI。這個信息如果可用,就是通過recurse和visit指令來查找存儲控制宏的FTL命名空間。
? ? 在FTL這里,節點屬性的直接使用可以通過內建函數node完成,還有visit和recurse宏。
對象包裝:
當往容器中添加一些對象時,正如在FreeMarker API文檔中看到的那樣,它可以收到任意java對象類型的參數,而不一定是TemplateModel。這是因為模板實現時會默默地用合適的TemplateModel對象來替換原有對象。比如向容器中加入一個String,也許它將被替換為一個SimpleScalar實例來存儲相同的文本。
至于替換什么時候發生,這就是容器業務處理的問題(類的業務實現了容器接口)所在,但是它在獲取子變量時必須會發生,因為getter方法(依據接口而定)會返回TemplateModel,而不是Object。SimpleHash,SimpleSequence和SimpleCollection使用最懶的策略,當第一次獲取子變量時,它們用一個適合的TemplateModel來替換一個非TemplateModel子變量。
至于什么類型的Java對象可以被替換,又使用什么樣的TemplateModel來實現,它可以被實現的容器自身來控制,也可以委派給ObjectWrapper的一個實例。ObjectWrapper是一個接口,其中只定義了一個方法:TemplateModel wrap(java.lang.Object obj)??梢詡鬟f一個Object類型的參數,它會返回對應的TemplateModel對象,如果不行則拋出TemplateModelException異常。替換原則是在ObjectWrapper的實現類中編碼實現的。
最重要的ObjectWrapper實現類是FreeMarker核心包提供的:
? ? ? ObjectWrapper.DEFAULT_WRAPPER:它使用SimpleScalar來替換String,SimpleNumber來替換Number,SimpleSequence來替換List和數組,SimpleHash來替換Map,TemplateBooleanModel.TRUE或TemplateBooleanModel.FALSE來替換Boolean,freemarker.ext.dom.NodeModel來替換W3C組織定義的DOM模型節點類型。對于Jython類型的對象,包裝器會調用freemarker.ext.jython.JythonWrapper。而對于其他對象,則會調用BEAN_WRAPPER。
? ? ? ObjectWrapper.BEANS_WRAPPER:它可以通過Java 的反射機制來獲取到Java Bean的屬性和其他任意對象類型的成員變量。在最新的FreeMarker 2.3版本中,它是freemarker.ext.beans.BeansWrapper的實例。
? ? 做一個具體的例子,讓我們來看看SimpleXxx類型都是怎么工作的。SimpleHash,SimpleSequence和SimpleCollection使用DEFAULT_WRAPPER來包裝子變量(除非在構造方法中傳遞另外一個包裝器)。這個例子在實戰中來展示DEFAULT_WRAPPER。
Map map = new HashMap(); map.put("anotherString", "blah"); map.put("anotherNumber", new Double(3.14)); List list = new ArrayList(); list.add("red"); list.add("green"); list.add("blue"); SimpleHash root = new SimpleHash(); // 將會使用默認的包裝器 root.put("theString", "wombat"); root.put("theNumber", new Integer(8)); root.put("theMap", map); root.put("theList", list);
假設root是數據模型的root,那么得到的數據模型將是:
注意在theMap和theList中的Object也可以作為子變量來訪問。這是因為,當要訪問theMap.anotherString時,SimpleHash(這里作為根哈希表)會靜默地使用SimpleHash實例來替換Map(theMap),這個實例使用了和根哈希表相同的包裝器。所以當訪問其中的子變量anotherString時,就會使用SimpleScalar來替換它。
如果在數據模型中放了任意的對象,那么DEFAULT_WRAPPER就會調用BEANS_WRAPPER來包裝這個對象:
SimpleHash root = new SimpleHash();
// 可以拿到Java對象"simple":
root.put("theString", "wombat");
// 可以拿到Java對象":
root.put("theObject", new TestObject("green mouse", 1200));
假設TestObject是這樣的:
?
[java]?view plaincopy public class TestObject { private String name; private int price; public TestObject(String name, int price) { this.name = name; this.price = price; } // JavaBean的屬性 // 注意公有字段不能直接可見; // 你必須為它們編寫getter方法。 public String getName() { return name; } public int getPrice() { return price; } // 一個方法 public double sin(double x) { return Math.sin(x); } }
數據模型就會是這樣:
?
我們可以這樣把它和模板合并:
${theObject.name}
${theObject.price}
${theObject.sin(123)}
輸出將如下:
green mouse
1200
-0,45990349068959124
之前我們已經看到了,我們使用java.util.HashMap作為根哈希表,而不是SimpleHash或其他特定的FreeMarker類。因為Template.process(...)自動包裝了給定的數據模型參數的對象,所以它才會起作用。它使用受Configuration級設置的對象包裝器,object_wrapper(除非明確指定一個ObjectWrapper作為它的參數)。因此,編寫簡單的FreeMarker應用程序就不需要知道TemplateModel了。注意根的類型不需要一定是java.util.Map。它也可以是實現了TemplateHashModel接口的被包裝的對象。
object_wrapper設置的默認值是ObjectWrapper.DEFAULT_WRAPPER。如果想改變它,比如換成ObjectWrapper.BEANS_WRAPPER,那么可以這樣來配置FreeMarker引擎(在其它線程開始使用它之前):
cfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
要注意我們可以在這里設置任何對象實現接口ObjectWrapper,當然也可以用來設置你自己定義的實現類。
對于包裝了基本Java容器類型(比如java.util.Map和java.util.List)的TemplateModel實現類,常規是它們使用像它們父容器那樣的相同對象包裝器來包裝它們的子變量。從技術上講,它們是被父容器(它對所創建的子類有全部的控制器)實例化的,因為父容器創建了它們,所以它們使用和父容器一樣的對象包裝器。如果BEANS_WRAPPER用來包裝根哈希表,那么它也會被用來包裝子變量(子變量的子變量也是如此,以此類推)。這個之前看到的theMap.anotherString是同樣的現象。
轉載于:https://www.cnblogs.com/laobiao/p/5596924.html
總結
以上是生活随笔為你收集整理的freemarker程序开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 查找抄袭文章
- 下一篇: 获取iframe中的contentWin