在之前的章節(jié)里,我們討論了所有Velocity的指令,包括#macro指令。然而,我們只是簡要介紹了#macro指令。在這里,我們將重新討論#macro指令(也可以稱為Velocimacros)。我們將討論參數(shù)傳遞、宏庫、運行時配置和其他與Velocimacros相關(guān)的主題。
參數(shù)傳遞Argument Passing
當(dāng)我們在Chapter 8里介紹#macro指令時,我們展示了一個簡單的Hello World示例。The template used for that example defines an inline Velocimacro that outputs Hello world each time it is invoked. This macro is defined so that no arguments are allowed in the macro invocation. Note that the #macro directive itself always requires at least one argument, the first of which specifies the name used to invoke the macro. However, the invocation of a macro takes zero or more arguments, depending on its specification.(示例里使用的模板定義了一個用于輸出Hello world的內(nèi)聯(lián)Velocimacro。這個宏定義為沒有參數(shù)。注意,#macro指令最小需要一個參數(shù),第一個參數(shù)用于指定調(diào)用宏的名稱。然而,宏的invocation獲得0個或更多參數(shù),完全依賴于它的規(guī)范)。
為了允許把參數(shù)傳遞到Velocimacro,你只需要一個為每一個需要傳遞的參數(shù)提供了名稱的#macro指令,這些參數(shù)用空格進(jìn)行分隔。每一個引用名可以作為Velocity變量引用用于宏的代碼塊。和#foreach和#if關(guān)系指令一樣,代碼塊用#end指令終止。為了演示這個例子,我們把Listing 8.40示例的Velocimacro部分進(jìn)行改動,具體改動為:把簡單的輸出hello改為輸出個人詳細(xì)信息。這個新改進(jìn)的宏見Listing 9.1。在處理的時候,這個模板將在字符串“Hello Zaphod!”之后新增一個字符串“Hello Arthur!”。
## Define inline macro for this template
#macro( sayHiTo $who )
Hello $who!
#end
## Invoke the macro using normal directive syntax
#sayHiTo( "Arthur" )
#sayHiTo( "Zaphod" )
Listing 9.1 A template demonstrating the #macro directive with support for argument passing.
為了能夠成功調(diào)用Velocimacro,invocation的參數(shù)個數(shù)必須和宏定義指定的參數(shù)個數(shù)匹配。比如,如果Listing 9.1里的宏被作為#sayHiTo() 或#say-HiTo(“Zaphod” “Beeblebrox”)進(jìn)行調(diào)用,則invocation將被忽略。由此得出結(jié)論,Velocimacro不支持默認(rèn)參數(shù);也不支持重載。同樣地,在Velocimacro 故意改變參數(shù)個數(shù)的情況下,這兒并沒有什么獨特的解決方法。在很多類似情況下,為宏指定唯一名稱是必需的。
迄今為止,我們僅僅演示了使用字符串作為輸入?yún)?shù)。其實,Velocimacro也支持integer、布爾類型、值域操作符、數(shù)組列表和Velocity引用。Listing 9.2展示了使用以上所有類型作為輸入?yún)?shù)的模板。第一個Velocimacro需要使用字符串、integer和布爾類型。第二個需要使用值域操作符和數(shù)組列表。最后一個需要使用Velocity引用。注意,這些宏定義和invocation的參數(shù)列表都是用空格來定界的,和許多通用編程語言使用逗號進(jìn)行分隔一樣。宏只是簡單輸出提供的參數(shù)值,見Listing 9.3。
## Define Velocimacros
## (string literal - integer literal - boolean)
#macro( sib $string $int $bool )
The string is $string.
The integer is $int.
The boolean is $bool.
#end
## (range - array list)
#macro( ra $range $arrayList )
#foreach ( $val in $range ) $val #end
#foreach ( $val in $arrayList ) $val #end
#end
## (Velocity reference)
#macro( r $vref )
The reference correspond to $vref
#end
## Invoke Velocimacros
#sib( "Hello" 42 true )
#ra( [-9..-1] ["favorite", "color"] )
#set( $color = "Blue. No! Yellow!" )
#r( $color )
Listing 9.2 A template demonstrating the #macro directive used with various argument types.
The string is Hello.
The integer is 42.
The boolean is true.
-9 -8 -7 -6 -5 -4 -3 -2 -1
favorite color
The reference correspond to Blue. No! Yellow!
Listing 9.3 Results from processing the template in Listing 9.2.
我們在前面的示例里已經(jīng)演示了Velocimacro直接支持的多種類型參數(shù)。下面我們將討論用Velocity引用作為Velocimacro的輸入?yún)?shù)。把引用作為宏的輸入?yún)?shù)并不僅限于變量引用,還可以使用Velocity方法引用和Velocity屬性引用。雖然使用這些引用作為輸入?yún)?shù)是可行的,但在使用中仍然要小心。正確使用這些引用的訣竅就是深入理解他們是通過名字進(jìn)行參數(shù)傳遞的(pass by name)。That is, the reference is not evaluated until after it is received by the Velocimacro。為了闡明這些概念,讓我們來考慮FromFive類(見Listing 9.4),這個類簡單初始化一個int屬性,賦值為5,每一次通過類的getNext()方法請求時,將消耗一點這個值。
public class FromFive
{
public FromFive()
{
nextValue = 5;
}
public Integer getNext()
{
return (new Integer( nextValue-- ));
}
public String toString()
{
return (String.valueOf( nextValue ));
}
private int nextValue;
}
Listing 9.4 The FromFive class, which generates a sequence of decreasing integer values.
接著,假定我們的應(yīng)用程序使用三個FromFive類的實例(對應(yīng)關(guān)鍵字為字符串為method-Ref、propRef和varRef)來維護(hù)Velocity上下文組裝。如果這個應(yīng)用程序需要處理的模板為Listing 9.5所示,其輸出結(jié)果為Listing 9.6。As the output demonstrates, the value associated with the $ref reference in the countdown Velocimacro changes each time it is evaluated for the cases of method and property reference input. This is due to the fact that in these two cases $ref stores the names $methodRef.getNext() and $propRef.next, respectively, rather than the values those references evaluate to. In effect, $ref simply becomes an alias for the provided method or property reference. Although not as obvious, the same is in fact true for the variable reference $varRef; the associated output differs only due to the fact that FromFive’s toString() method does not decrement the value of an object’s nextValue property. If toString() were modified to behave in the same manner as getNext(), then passing $varRef to the countDown Velocimacro would also result in decrementing output.
## Evaluate provided reference six times.
#macro( countDown $ref )
$ref.. $ref.. $ref.. $ref.. $ref.. $ref
#end
## Call countDown with a method reference
#countDown( $methodRef.getNext() )
## Call countDown with a property reference
#countDown( $propRef.next )
## Call countDown with a variable reference
#countDown( $varRef )
Listing 9.5 A template demonstrating the use of all three types of Velocity references with the #macro directive.
5.. 4.. 3.. 2.. 1.. 0
5.. 4.. 3.. 2.. 1.. 0
5.. 5.. 5.. 5.. 5.. 5
Listing 9.6 Results from processing the template in Listing 9.5.
如果必需要通過值類型傳遞(pass by value)方式來傳遞Velocity引用的判斷結(jié)果值,最容易的解決方法是#set指令來捕獲這個獨立的變量引用值,之后傳遞那個引用。讓我們來看一下Listing 9.7里的模板,這個模板的內(nèi)聯(lián)宏定義和Listing 9.5相同。然而,我們改變invocation來模仿值類型傳遞(pass-by-value)行為。注意,這個新的模板只通過#set指令途徑來仿效值類型傳遞。在下面的封裝,調(diào)用仍然是名字傳遞(pass-by-name)。
## Evaluate provided reference six times.
#macro( countDown $ref )
$ref.. $ref.. $ref.. $ref.. $ref.. $ref
#end
## Call countDown with the value of a method reference
#set( $methodValue = $methodRef.getNext() )
#countDown( $methodValue )
## Call countDown with the value of a property reference
#set( $propValue = $propRef.next )
#countDown( $propValue )
## Call countDown with the value of a variable reference
#set( $varValue = $varRef )
#countDown( $varValue )
Listing 9.7 A template demonstrating emulated pass-by-value behavior with a #macro directive.
Inline vs. Library Macros
迄今為止,我們的Velocimacro示例都集中在內(nèi)聯(lián)定義上。雖然內(nèi)聯(lián)定義可以用于很多場合,但其固有的特性限制了內(nèi)聯(lián)定義只能用于代碼再使用。內(nèi)聯(lián)Velocimacro的作用域僅限于其定義的模板文件,更明確一點,只限于具有宏定義的那部分模板文件。因此,其他模板文件不能訪問這樣的宏。如果嘗試通過#include或#parse指令把一個模板中的Velocimacro定義應(yīng)用到其他作用域中時,將會失敗。
#include指令導(dǎo)入的只是靜態(tài)內(nèi)容,因此#macro指令將失去其特定的意義。#parse指令把包含進(jìn)來的文本當(dāng)作普通模板代碼來處理。與此相反,當(dāng)模板第一次被模板引擎解析時,Velocimacro調(diào)用被終止,在任何#parse指令導(dǎo)入外部的Velocimacro定義時。(The #include directive imports only static content, so #macro directives would lose any special meaning. The #parse directive does process the included text as normal template code, but it does so at runtime. In contrast, Velocimacro calls are determined when the template is first parsed by the template engine, well before any #parse directives have a chance to import external Velocimacro definitions)。
因此,如何通過多模板共享Velocimacro,從而避免單調(diào)重復(fù)的拷貝和粘貼?答案就是Velocity對宏庫的支持。這個特性允許你創(chuàng)建多宏庫,創(chuàng)建方法是應(yīng)用程序通過Velocimacro屬性的變量進(jìn)行注冊來創(chuàng)建。一旦這樣的庫被注冊,那么應(yīng)用程序處理的任何模板都可以庫中調(diào)用這個Velocimacro。我們將在本章稍后討論Velocimacro屬性,并在Chapter 10 (“Taking Control of Velocity”)對Velocity的屬性系統(tǒng)進(jìn)行討論?,F(xiàn)在,我們只需了解,通過屬性系統(tǒng),包含#macro指令的文件可以作為Velocimacro庫,讓所有的模板訪問。
(如果你的Velocimacro庫需求是適度的,那么)根本就不必為Velocity的屬性系統(tǒng)煩擾。默認(rèn)情況下,模板引擎會把任何一個文件假定為位于應(yīng)用程序目錄下的VM_global_library.vm文件,并將其解釋為宏庫。比如,如果我們的模板(來自Listing 9.5)被分割成兩個Listings 9.8和Listings9.9的文件,輸出結(jié)果仍是一樣的(見Listing 9.6)。僅僅需要把Velocimacro庫命名成VM_global_library.vm即可。
## Evaluate provided reference six times.
#macro( countDown $ref )
$ref.. $ref.. $ref.. $ref.. $ref.. $ref
#end
Listing 9.8 A macro library containing the Velocimacro originally defined in Listing 9.5. If Velocity defaults are assumed, this file must be named VM_global_library.vm.
## Call countDown with the value of a method reference
#countDown( $methodRef.getNext() )
## Call countDown with the value of a property reference
#countDown( $propRef.next )
## Call countDown with the value of a variable reference
#countDown( $varRef )
Listing 9.9 The template from Listing 9.5 after moving its Velocimacro to a macro library.
Velocimacro Properties
Velocity提供了許多配置屬性,用于調(diào)整Velocimacros的行為。我們將在Chapter 10討論這些屬性的具體功能,現(xiàn)在,我們只了解其名稱、說明和默認(rèn)設(shè)置。
velocimacro.library
我們曾經(jīng)在前面章節(jié)里提到過庫屬性。這個屬性用于定義應(yīng)用程序Velocimacros庫里文件的名稱,該文件名稱是相對于當(dāng)前模板路徑配置的。如果有多個文件需要包含到庫里,那么在這些文件名之間用逗號分隔。庫屬性的默認(rèn)值是VM_global_library.vm。
velocimacro.permissions.allow.inline
permissions.allow.inline屬性用于定義是否允許內(nèi)聯(lián)(inline)Velocimacro定義。也就是說,是否可以把Velocimacro宏定義在一個非庫模板文件里。如果為false,內(nèi)聯(lián)Velocimacro定義將導(dǎo)致日志警告信息,并且該定義將被模板引擎忽略。默認(rèn)值為true。
velocimacro.permissions.allow.inline.to.replace.global
permissions.allow.inline.to.replace.global屬性用于定義是否允許內(nèi)聯(lián)(inline) Velocimacro用相同的名稱去重載一個Velocimacro庫。當(dāng)然,只有在permissions.allow.inline屬性為true里才有意義,否則,內(nèi)聯(lián)定義就不能通過。如果permissions. allow.inline.to.replace.global屬性為true,和內(nèi)聯(lián)Velocimacro 同名的Velocimacro庫將被內(nèi)聯(lián)Velocimacro替代。如果為false,Velocimacro庫將受到保護(hù),不管有意還是無意的內(nèi)聯(lián)Velocimacro庫都不能替代Velocimacro庫。默認(rèn)值為false。
velocimacro.permissions.allow.inline.local.scope
permissions.allow.inline.local.scope屬性用于定義是否允許模板為Velocimacro提供私有(private)命名空間。當(dāng)這個屬性為true時,就允許提供私有命名空間,同時內(nèi)聯(lián)Velocimacro定義可用于定義模板。無論何時Velocimacro定義被請求時,允許私有命名空間也將導(dǎo)致模板的命名空間首先被搜索。最后一個特性是允許本地Velocimacro定義重載任何其他在外部模板定義的定義。默認(rèn)值是false。
velocimacro.context.localscope
context.localscope屬性用于定義#set指令影響在Velocimacro 中使用Velocity上下文的方式。當(dāng)屬性值為true時,Velocimacro將有效接收它自己本地的上下文。調(diào)用者的對象上下文關(guān)鍵字對Velocimacro來說不可見,轉(zhuǎn)而變成使用來自Velocimacro內(nèi)部的上下文,而且不復(fù)制回轉(zhuǎn)給調(diào)用者。與此相反,當(dāng)屬性值為false時,將把調(diào)用者的上下文放到一個Velocimacro可達(dá)到的、可更改的地方。也就是說Velocimacro將使用調(diào)用者的上下文。默認(rèn)值為false。
velocimacro.library.autoreload
library.autoreload屬性用于定義當(dāng)庫中的宏被調(diào)用時,是否允許自動重新加載這個修改過的Velocimacro庫。如果屬性值為true,每一次調(diào)用Velocimacro庫都將檢查相應(yīng)庫的修改信息,如果檢查發(fā)現(xiàn)這個庫在最后一次加載后被修改過,Velocity將自動重新加載這個庫。如果屬性值為false,Velocimacro庫一旦加載后就不再進(jìn)行修改信息檢測。默認(rèn)值為false。該重加載功能主要用于程序測試和調(diào)試。如果你需要允許這個屬性,那么很可能你也需要禁止資源加載緩存,我們將在下一章討論屬性控制這個緩存。
velocimacro.messages.on
messages.on屬性用于指定是否讓模板引擎產(chǎn)生附加的關(guān)于Velocimacros的日志信息。當(dāng)這個屬性的值為true時,就需要產(chǎn)生附加信息;為false時,將不產(chǎn)生附加信息;默認(rèn)值為true。
嵌套和遞歸
迄今為止,本章所有的Velocimacro示例都僅限于不使用嵌套和遞歸的情況。嵌套,最簡單的情況就是在Velocimacro里調(diào)用另外一個Velocimacro,這是在實際代碼開發(fā)中使用得最頻繁的一種。遞歸,是一種特殊類型的嵌套,它是在Velocimacro里調(diào)用自身,但這種情況并不太常見。幸運的是,Velocimacro對嵌套和遞歸都提供了支持。Listing 9.10里演示了Velocimacro使用嵌套和遞歸的例子。Velocimacro重復(fù)調(diào)用它自己,調(diào)用次數(shù)由它的參數(shù)$depth指定。writeABC宏通過調(diào)用getA和getBC Velocimacros演示了Velocimacro嵌套。Listing 9.11顯示了輸出結(jié)果。
## A recursive Velocimacro
#macro( recurs $depth )
Entering at level $depth
#set( $depth = $depth - 1 )
#if ( $depth > 0 )
#recurs( $depth )
#end
#set( $depth = $depth + 1 )
Leaving from level $depth
#end
## Nesting of Velocimacros
#macro( getA ) A #end
#macro( getB ) B #end
#macro( getC ) C #end
#macro( getBC )
#getB()#getC()
#end
#macro( writeABC )
#getA()#getBC()
#end
#recurs( 3 )
#writeABC()
Listing 9.10 A template demonstrating the use of nesting and recursion of Velocimacros.
Entering at level 3
Entering at level 2
Entering at level 1
Leaving from level 1
Leaving from level 2
Leaving from level 3
A B C
Listing 9.11 Results from processing the template in Listing 9.10.
本章小節(jié)和下章介紹
在這一章里,我們對#macro指令(或叫Velocimacro)展開了深入的討論。討論覆蓋了參數(shù)傳遞、宏庫、嵌套和遞歸、以及Velocimacro屬性。這在一點上,你應(yīng)該已經(jīng)對Velocity的上下文、模板語言以及引用和指令有了較好的理解。你現(xiàn)在應(yīng)該已經(jīng)可以開發(fā)模板了。然而,仍有許多Velocity功能需要你去了解。這就我們下一章將要討論的內(nèi)容。