Velocity官方指南-容器
原文網址 譯者:曾道濤
簡介
“容器”這一概念對於Velocity來說很重要,它是在系統的各部分之間傳遞一系列資料的通用技術。也就是說,容器是Java層(或者程式設計師)和模板層(或者設計師)之間的資料搬運工。作為程式設計師,你會收集各種型別的物件,包括所有你程式需要的,然後把它們放在容器裡。對於設計師來說,這些物件,以及它們的方法和屬性,可以通過被稱為引用的模板元素來訪問。一般來說,你會和設計師一起決定應用程式的資料需求。從某種意義上說,一旦你為設計師生成了一個數據集,也即在模板中提供了“API”訪問。因此,在這個階段的開發過程中,你值得花時間仔細分析。
雖然Velocity允許你建立自己的容器類來滿足特殊的需求和技術(比如像一個直接訪問LDAP伺服器的容器),一個叫VelocityContext的基本實現類已經作為發行版的一部分提供給你。
VelocityContext適合所有的一般需求,所以我們強烈推薦你使用它。只有在特殊情況和高階應用中,才需要你擴充套件或者建立你自己的容器實現。
使用VelocityContext就像使用一個普通的Java Hashtable類一樣簡單。雖然介面包含其他有用的方法,我們使用的兩個主要方法是:
public Object put(String key, Object value); public Object get(String key);
請注意,引數value必須繼承自java.lang.Object。基本型別像int和float必須用合適的包裝類包裝。
這就是所有關於容器的基本操作。如需更多資訊,可以檢視發行版中包含的API文件。
for和foreach()遍歷物件的支援
作為一個程式設計師,對於你放在容器中的物件有很大的自主權。但是正如大多數自主權,這也需要一點點責任,所以要理解Velocity支援什麼,以及任何可能出現的問題。Velocity支援幾種集合型別在VTL中使用foreach()語句。
• Object [] 普通物件陣列,在這裡無需多說。如果一個類中提供了迭代器介面,Velocity會自動包裝你的陣列,但是這不應該涉及到程式設計師和模板的設計者。更有趣的是,Velocity現在允許模板設計者把陣列當作定長連結串列來處理(Velocity 1.6中就是這樣)。這意味著他們既可以在陣列上也可以在java.util.List的例項上呼叫像size(), isEmpty()和get(int)這樣的方法,而無需關心它們本身的差異。
• java.util.Collection Velocity通過iterator()方法返回一個迭代器在迴圈中使用,所以如果你正在你的物件上實現一個集合介面,請確保iterator()方法返回一個有效的迭代器。
• java.util.Map 這裡,Velocity通過介面的values()方法返回一個Collection介面,iterator()方法在它上面呼叫來檢索用於迴圈的迭代器。
• java.util.Iterator 使用的時候需要注意:目前只是暫時支援,關注的問題是迭代器不能重置。如果一個未初始化的迭代器被放進了容器,並且在多個foreach()語句中使用,如果第一個foreach()失敗了,後面的都會被阻塞,因為迭代器不會重啟。
• java.util.Enumeration 使用的時候需要注意:和java.util.Iterator一樣,目前只是暫時支援,關注的問題是列舉不能重置。如果一個未初始化的列舉被放進了容器,並且在多個foreach()語句中使用,如果第一個foreach()失敗了,後面的都會被阻塞,因為列舉不會重啟。
• 任何擁有public Iterator iterator()方法的公有類永遠不會返回null。Velocity會把尋找一個iterator()方法作為最後的手段。這提供了很大的靈活性,也自動支援Java 1.5中的java.util.Iterable介面。
對於Iterator和Enumeration,推薦只有在萬不得已的情況下才把它們放進容器,你也應該儘可能地讓Velocity找到合適的、可複用的迭代介面。
雖然有充足的理由直接使用java.util.Iterator介面(比如像JDBC這樣的大資料集),但是如果能夠避免,用其他的可能會更好。“直接”是說像下面這樣:
Vector v = new Vector(); v.addElement("Hello"); v.addElement("There"); context.put("words", v.iterator() );
當迭代器本身被放進了容器。當然,你也可以簡單地這樣做:
context.put("words", v );
兩種方式都可以:Velocity能夠識別實現了Collection(像List)介面的Vector,並因此找到iterator()方法,在它每次需要的時候呼叫iterator()來重新整理迭代器。一旦Velocity在foreach()中使用了一個空迭代器(先跳過這裡…),Velocity沒辦法為它正在使用的下一個foreach()獲取一個新的迭代器。結果是後面任何使用空迭代器引用的foreach()都會阻塞,並且沒有輸出。
上面這些並不是為了表明在Velocity中遍歷集合需要十分小心。恰恰相反,一般來說,只要在你向容器中新增迭代器時小心就可以了。
對靜態類的支援
並非所有的類都可以例項化。像java.lang.Math這樣的類不提供任何公有的建構函式,但是它包含了有用的靜態方法。為了從模板中訪問這些靜態方法,你可以簡單地把這些類自身新增到容器中:
context.put("Math", Math.class);
這樣你就可以在模板中用$Math引用呼叫java.lang.Math中的任何公有靜態方法。
容器鏈
Velocity容器設計的一大創新特性是容器鏈的概念。有時也被稱為contextwrapping,這個高階特性允許你以一種方式把獨立的容器連線在一起,使它們對模板來說像一個連續的容器。
最好用一個例子來說明:
VelocityContext context1 = new VelocityContext(); context1.put("name","Velocity"); context1.put("project", "Jakarta"); context1.put("duplicate", "I am in context1"); VelocityContext context2 = new VelocityContext( context1 ); context2.put("lang", "Java" ); context2.put("duplicate", "I am in context2"); template.merge( context2, writer );
在上面這段程式碼中,我們建立context2,使它連線context1。這意味著在模板中,你可以訪問這兩個VelocityContext物件中放置的任意項,只要在新增物件的時候沒有重複的鍵。如果遇到這種情況,就像上面的鍵”duplicate”,最近的容器中儲存的物件可以被訪問。在上面這個例子中,返回的物件是字串”I am in context2″。
注意,這種容器中物件的重複或者“覆蓋”,不會以任何方式損壞或者修改被覆蓋的物件。所以在上面的例子中, 字串”I am in context1″還存在並且完好無損,仍然可以通過context1.get(“duplicate”)訪問。但是在上面的例子中,模板中引用”$duplicate”的值會是”I am in context2″,模板不能訪問被覆蓋的字串”I am in context1″。
也要注意,當你使用模板向一個渲染之後再檢查的容器中新增資訊的時候,你必須要小心。在一個模板中,通過set()語句改變容器只會影響外層的容器。所以在期望模板中的資料已經被新增到內部容器時,請確保你沒有丟棄外層的容器。
這個特性有很多用途,目前最常用的就是提供層次資料訪問和工具箱。
就像前面提到過的,Velocity容器機制是可以擴充套件的,但是超出了本指南的當前範圍。如果你有興趣,可以檢視包org.apache.velocity.context中的類,看看提供的容器怎麼組合在一起。更進一步,有幾個例子在發行版的examples/context_example目錄下,它們展示了可能的實現,包括一個使用資料庫作為後臺儲存的例子。
請注意,這些例子不被支援,它們僅僅是出於示範和教學目的。
模板中建立的物件
在模板中,有兩種情況Java程式碼必須處理執行時建立的物件:
當模板設計者通過Java程式碼呼叫容器中放置的物件的方法:
#set($myarr = [“a”,”b”,”c”] )
$foo.bar( $myarr )
當模板向容器中新增物件,Java程式碼可以在合併過程完成後訪問這些物件:
#set($myarr = [“a”,”b”,”c”] )
#set( $foo = 1 )
#set( $bar = “bar”)
如果非常直接地處理這些情況,有幾個事情需要知道:
• 當放在容器中或者傳給方法時,VTL RangeOperator [ 1..10 ]和ObjectArray [“a”,”b”]是java.util.ArrayList物件。所以,當你設計接受模板中建立的陣列的方法時,你應該牢記這個問題。
• VTL Map引用毫無疑問是用java.util.Map儲存的。
• 小數在容器中會是Doubles或者BigDecimals,整數會是 Integer, Long,或者BigIntegers字串當然還是Strings。
• Velocity在呼叫方法時會適當地省略引數,所以呼叫setFoo( int i )把一個整數放入容器,#set()和它等價。
其他容器問題
VelocityContext(或者任何派生自AbstractContext的Context)提供的一個特性是節點特有的自我監視快取。一般來說,作為一個開發者,當你使用VelocityContext作為你的容器時,不必擔心這些。但是,有一個目前已知的使用模式,你必須知道這個特性。
當VelocityContex訪問模板中的節點時,它會收集關於這些有序節點的自我監視資訊。所以,在下面這幾種情況:
• 你使用同一個VelocityContext物件遍歷同樣的模板。
• 模板快取關閉。
• 在每次迭代中,你通過getTemplate()請求模板。
你的VelocityContext有可能出現記憶體洩露(在收集更多自我監視資訊時真會出現)。真正發生的是,它為每個它訪問的模板收集模板節點的自我監視資訊,當模板快取關閉時,對VelocityContext來說它每次都是訪問一個新模板。因此,它收集更多的自我監視資訊,並且一直增長。強烈推薦你做以下一條或者更多條:
• 通過模板補償處理,每次遍歷都新建一個VelocityContext。這樣就不會收集自我監視快取資料。在你由於VelocityContext已經填充了資料或物件而想重用它的情況下,你可以簡單地把填充好的VelocityContext包裝成另一個VelocityContext,外層的那個會收集自我監視資訊,你直接丟棄就可以了。例如 VelocityContext useThis = new VelocityContext( populatedVC );它可以很好地工作,因為外層的容器會儲存自我監視快取資料,它可以從內部容器獲取任何請求的資料(當它為空時)。你仍然要注意,如果你的模板把資料放入容器並且期待後面的遍歷使用這些資料,你需要做另外一個準備,因為任何模板set()語句會被儲存在最外層的容器。看“Context chaining”章節中的討論來獲取更多資訊。
• 開啟模板快取。這樣可以避免在每次遍歷中重複解析模板,因此VelocityContext不僅可以避免增加自我監視的快取資訊,還可以帶來效能的提升。
• 在迴圈遍歷的過程中重用模板物件。如果快取關閉了,你不必讓Velocity一遍又一遍地重複讀取和解析相同的模板,所以VelocityContext就不會每次都收集新的自我監視資訊。