Velocity開發(fā)者指南
(學(xué)習(xí)筆記)
一、Velocity 的工作原理
基本模式
當(dāng)我們?cè)?/SPAN>Application或Servlet(實(shí)際上包括其他任何形式)中使用Velocity時(shí),通常會(huì)做如下幾件事:
·初始化Velocity。適用于Velocity的兩種應(yīng)用模式-單例(Singleton)和(separate runtime instance),并且你僅需要執(zhí)行一次。
·創(chuàng)建一個(gè)Context對(duì)象。
·向Context對(duì)象添加數(shù)據(jù)。
·選擇一個(gè)模板。
·合并模板和數(shù)據(jù)并輸出。
通過(guò)org.apache.velocity.app.Velocity類,可以象這樣在你的代碼里應(yīng)用單例模式:
import java.io.StringWriter;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.MethodInvocationException;
Velocity.init();
VelocityContext context = new VelocityContext();
context.put("name",new String("Velocity"));
Template template = null;
try{
template = Velocity.getTemplate("mytemplate.vm");
}catch(ResourceNotFoundException rnfe){
//couldn't find the template
}catch(ParseErrorException pee){
//syntax error : problem parsing the template
}catch(MethodInvocationException mie){
//someing invoked in the template
//threw an exception
}catch(Exception e){}
StringWriter writer = new StringWriter();
template.merge(context,writer); |
這是最基本的使用方式,非常簡(jiǎn)單!但這恰恰就是當(dāng)你用Velocity表現(xiàn)一個(gè)模版的時(shí)候所發(fā)生的事情!事實(shí)上,你并不一定要嚴(yán)格的按照這種方式寫代碼,因?yàn)槲覀優(yōu)?/SPAN>servlet和Application開發(fā)人員提供了一些更加簡(jiǎn)單的工具。
二、是否使用單例模式
在Velocity1.2和以后的版本中,開發(fā)人員有了兩中使用Velocity引擎的方式:?jiǎn)卫J?/SPAN>(singleton model)或分離實(shí)例模式(separate instance model)。兩種方式采用相同的核心Velocity代碼,這使得更加容易整和Velocity和Java應(yīng)用程序。
單例模式
在JVM或者web應(yīng)用中僅僅存在一個(gè)Velocity引擎的實(shí)例,并且所有應(yīng)用都共享它。這個(gè)實(shí)例允許本地化配置和資源共享,可以通過(guò)org.apache.velocity.app.Velocity類獲得這個(gè)單例,就象下面這樣:
import org.apache.velocity.app.Velocity;
import org.apache.velocity.Template;
...
/*
* Configure the engine - as an example, we are using
* ourselves as the logger - see logging examples
*/
Velocity.setProperty( Velocity.RUNTIME_LOG_LOGSYSTEM, this);
/*
* now initialize the engine
*/
Velocity.init();
...
Template t = Velocity.getTemplate("foo.vm");
|
org.apache.velocity.servlet.VelocityServlet基類采用了單例模式,這是一個(gè)用于幫助開發(fā)servlets的實(shí)用類。盡管繼承這個(gè)類是應(yīng)用Velocity開發(fā)Servlets的最常用、最方便的方法,但是你仍然可以自由選擇是否使用這個(gè)類。
分離實(shí)例
作為1.2版的一個(gè)新特性,你可以在同一個(gè)JVM或web應(yīng)用中創(chuàng)建、配置和使用任意多的Velocity實(shí)例。通過(guò)org.apache.velocity.app.VelocityEngine類來(lái)使用分離的實(shí)例。就象下面這樣:
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.Template;
...
/*
* create a new instance of the engine
*/
VelocityEngine ve = new VelocityEngine();
/*
* configure the engine. In this case, we are using
* ourselves as a logger (see logging examples..)
*/
ve.setProperty( VelocityEngine.RUNTIME_LOG_LOGSYSTEM, this);
/*
* initialize the engine
*/
ve.init();
...
Template t = ve.getTemplate("foo.vm");
|
正如你所看到的一樣,非常的簡(jiǎn)單易懂!除了改變一些簡(jiǎn)單的語(yǔ)法,在你的應(yīng)用中采用單例模式或分離實(shí)例模式不需要其他任何改變!
作為程序開發(fā)人員,你可以使用org.apache.velocity.app.Velocity和org.apache.velocity.app.VelocityEngine兩個(gè)類同Velocity內(nèi)部交互。但是,請(qǐng)記住,任何時(shí)候絕對(duì)不要在你的應(yīng)用程序中使用內(nèi)部的org.apache.velocity.runtime包中Runtime,RuntimeConstants,RuntimeSingleton或者RuntimeInstance類,因?yàn)樗鼈儍H僅是供內(nèi)部使用的,并且以后可能會(huì)改變!
三、上下文
基礎(chǔ)
上下文(context)是Velocity的核心概念,也是在系統(tǒng)各部分之間移動(dòng)“數(shù)據(jù)容器”的一種常用技術(shù)。context作為Java層和模板層的數(shù)據(jù)載體!作為開發(fā)人員,你需要收集你的應(yīng)用程序所需要的各種類型的對(duì)象,并把它們放在context中。作為設(shè)計(jì)者,可以通過(guò) 引用 來(lái)存取這些對(duì)象。通常,開發(fā)人員需要和設(shè)計(jì)人員一同研究決定應(yīng)用中需要的數(shù)據(jù)。因此,這種協(xié)同開發(fā)值得多花費(fèi)一些時(shí)間并仔細(xì)對(duì)需求進(jìn)行分析!
Velocity允許開發(fā)人員創(chuàng)建自己的context類來(lái)支持特殊需求或技術(shù)(LDAP server),并提供了一個(gè)基礎(chǔ)的實(shí)現(xiàn)類VelocityContext!VelocityContext適用于所有通常的需求,強(qiáng)烈建議使用它!僅僅在特殊和高級(jí)的案例中創(chuàng)建自己的context實(shí)現(xiàn)。
使用VelocityContext就象使用HashTable一樣簡(jiǎn)單,這個(gè)接口包含了許多有用的方法,最常用的是:
public Object put(String key,Object value);
public Object get(String key);
需要注意的是,如同HashTable一樣,值必須是Object類型的,并且不能為空!象 int,float等基本數(shù)據(jù)類型必須包裝成適當(dāng)?shù)念愵愋汀?/SPAN>
這里是一些基本的context操作,更多信息請(qǐng)查看API。
通過(guò)#foreach()對(duì)迭代對(duì)象的支持
作為開發(fā)人員,你可以把多種對(duì)象存儲(chǔ)到context中,但是也有一些限制,所以應(yīng)該理解Velocity支持什么類型,以及可能會(huì)產(chǎn)生什么問(wèn)題。Velocity可以在VTL的#foreach()方法中使用很多類型的集合。
·Object[]正常的對(duì)象數(shù)組。 Velocity會(huì)在內(nèi)部使用一個(gè)提供Iterator接口的類來(lái)包裝這個(gè)數(shù)組,但是開發(fā)人員和模板設(shè)計(jì)者不需要關(guān)系這個(gè)過(guò)程。
·java.util.Collection Velocity將調(diào)用iterator()方法獲得一個(gè)Iterator,所以如果你的類實(shí)現(xiàn)了一個(gè)Collection接口,請(qǐng)確信iterator()方法會(huì)返回一個(gè)可用的Iterator.。
·java.util.Map 這里,Velocity將通過(guò)values()方法獲得一個(gè)Collection接口,并通過(guò)這個(gè)接口調(diào)用iterator()方法獲得Iterator。
·java.util.Iterator 注意:這只是暫時(shí)被支持的,原因是Iterator不支持reset。如果一個(gè)Iterator被存儲(chǔ)在context中,并且被多個(gè)#foreach()方法調(diào)用,那么除第一個(gè)#foreach()方法外,其他的都將失敗,因?yàn)?/SPAN>Iterator不支持reset!
·java.util.Enumeration 同上
基于Iterator和Enumeration的限制,強(qiáng)烈建議僅僅在不可避免的時(shí)候才使用它們,并且如果可能的話,你應(yīng)該讓Velocity自己查找適當(dāng)?shù)目芍赜玫牡涌凇?/SPAN>
上下文鏈(Context Chaining)
Velocity的一個(gè)創(chuàng)新的特性就是Context Chaining 概念。有時(shí)被稱為Context Wrapping,這個(gè)高級(jí)特性允許你把分離的contexts通過(guò)一種方式連接起來(lái),對(duì)template而言就象是一個(gè)context一樣!
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連接起來(lái)。這意味著在模板中,你可以存取存放在VelocityContext中的任何對(duì)象,只要他們的key不重復(fù)就可以,如果key出現(xiàn)重復(fù)的話,那么context2中值將是可用的。但值得注意的是,實(shí)際上context1中重復(fù)的key并沒(méi)
有被改變或破壞,仍然可以通過(guò)context1.get(“duplicate”)方法獲得context1中deplicate的值!但請(qǐng)記住:在模板中沒(méi)有任何方法獲得context1中deplicate的值!同樣,當(dāng)你通過(guò)#set()方法向context中增加信息的時(shí)候,這個(gè)新的信息將被增加到最外層的context中的key中,所以,請(qǐng)不要試圖將信息通過(guò)#set()方法增加到內(nèi)層的context中!
在Template中創(chuàng)建對(duì)象
通常有兩種情況需要在JAVA代碼中處理由template在運(yùn)行時(shí)創(chuàng)建的對(duì)象:
·template調(diào)用由JAVA代碼放置到context中的對(duì)象的方法。
·JAVA代碼在合并后存取由template放置到context中的對(duì)象。
關(guān)于context的一些其他問(wèn)題
VelocityContext的一個(gè)特性是結(jié)點(diǎn)特殊的內(nèi)省緩存(introspection caching)。通常,作為開發(fā)人員可以放心的把VelocityContext做為context使用。但是,必須知道關(guān)于這個(gè)特性的一個(gè)使用模式:
VelocityContext會(huì)堆積它訪問(wèn)過(guò)的模板中的語(yǔ)法結(jié)點(diǎn)的內(nèi)省信息。所以在下述情況下:
·重復(fù)使用同一個(gè)VelocityContext迭代訪問(wèn)同一個(gè)模板
·模板緩存關(guān)閉
·在每個(gè)反復(fù)迭代中調(diào)用getTemplate()方法請(qǐng)求獲得Template
VelocityContext可能會(huì)導(dǎo)致內(nèi)存泄漏(實(shí)際上是聚集了太多的內(nèi)省信息)。原因是VelocityContext 會(huì)堆積它所訪問(wèn)過(guò)的每個(gè)模板的內(nèi)省信息,如果template緩存被關(guān)閉,將導(dǎo)致VelocityContext每次都訪問(wèn)一個(gè)新的模板,從而堆積更多的內(nèi)省信息。
強(qiáng)烈建議你做如下的事情:
·當(dāng)template處理結(jié)束后創(chuàng)建一個(gè)新的VelocityContext,這將阻止堆積內(nèi)省信息。如果你需要重用攜帶數(shù)據(jù)和對(duì)象的VelocityContext,可以簡(jiǎn)單的用另一個(gè)VelocityContext來(lái)包裝它。外層的VelocityContext將會(huì)堆積內(nèi)省的信息,但另人興奮的是你將丟棄它!
·開啟模板的緩存機(jī)制!這將阻止每次都對(duì)template重新解析,這樣VelocityContext不僅可以避免增加內(nèi)省信息,同時(shí)還可以改進(jìn)程序。
·在循環(huán)迭代期間重用Template對(duì)象。這樣,當(dāng)緩存關(guān)閉的時(shí)候就不用強(qiáng)迫Velocity一次又一次的去重新讀取和重新解析同樣的template,因此,VelocityContext也就不會(huì)每次都堆積新的內(nèi)省信息!
四、在Servlets中使用Velocity
Servlet編程
Velocity的一個(gè)主要的應(yīng)用領(lǐng)域就是JAVA Servlet。有很多理由可以說(shuō)明Velocity適合這個(gè)領(lǐng)域,最關(guān)鍵的是Velocity強(qiáng)制視圖層(VIEW)和代碼分離!
在servlet中使用velocity非常的簡(jiǎn)單。基本上你要做的就是繼承VelocityServlet基類和實(shí)現(xiàn)handRequest()方法。
從Velocity1.1開始,有兩個(gè)handRequest()方法:
public Template handRequest(Context)
這是舊的方法。這個(gè)方法要求返回一個(gè)有效的Template對(duì)象。如果無(wú)效或者為空,將會(huì)產(chǎn)生異常!同時(shí)error()方法會(huì)被調(diào)用!如果你希望產(chǎn)生異常后做一些其他的事情(比如重定向)可以重寫這個(gè)方法。強(qiáng)烈建議您使用新的方法!
public Template handRequest(HttpServletRequest,HttpServletResponse,Context)
這是新方法。與舊的方法不同,它可以返回一個(gè)null來(lái)說(shuō)明方法已經(jīng)執(zhí)行,并且Velocity什么都不做。
下面是一個(gè)簡(jiǎn)單的例子:
public class SampleServlet extends VelocityServlet
{
public Template handleRequest( HttpServletRequest request,
HttpServletResponse response,
Context context )
{
String p1 = "Jakarta";
String p2 = "Velocity";
Vector vec = new Vector();
vec.addElement( p1 );
vec.addElement( p2 );
context.put("list", vec );
Template template = null;
try
{
template = getTemplate("sample.vm");
}
catch( ResourceNotFoundException rnfe )
{
// couldn't find the template
}
catch( ParseErrorException pee )
{
// syntax error : problem parsing the template
}
catch( Exception e )
{}
return template;
}
}
|
是不是覺(jué)得很面熟?除了創(chuàng)建context對(duì)象,這已經(jīng)由VelocityServelt幫你做了,并且VelocityServlet也幫你實(shí)現(xiàn)了merge()方法,這和我們開始部分的編碼方式基本上是一樣的。我們獲得context和應(yīng)用程序數(shù)據(jù),并反回一個(gè)template。
默認(rèn)傳遞給handleRequest()方法的context對(duì)象包含當(dāng)前的HttpServletRequest和HttpServletResponse對(duì)象。他們分別被放置在常量VelocityServlet.REQUEST和VelocityServlet.RESPONSE中。你可以在JAVA代碼中獲得并使用他們:
public Template handleRequest( Context context )
{
HttpServletRequest request = (HttpServletRequest) context.get( REQUEST );
HttpServletResponse response = (HttpServletResponse) context.get( RESPONSE );
...
|
也可以在template中使用
#set($name = $req.getParameter('name') ) |
VelocityServelt允許開發(fā)者重寫它的一些方法,以更好的使用它。
Properties loadConfiguration(ServletConfig)
這個(gè)方法允許重寫通常的配置機(jī)制,增加或修改配置屬性。
Context createContext(HttpServletRequest,HttpServletResponse)
這個(gè)方法允許你創(chuàng)建自己的Context對(duì)象。默認(rèn)的實(shí)現(xiàn)只是簡(jiǎn)單的包含request和response的VelocityContext對(duì)象。為了避免在一些servlet容器中發(fā)生內(nèi)省緩存問(wèn)題,這個(gè)resquest和response對(duì)象被一個(gè)簡(jiǎn)單的包裹器類包裝了。你可以正常的使用他們,但是請(qǐng)注意他們并不是javax.servlet.XXX !
void mergeTemplage(Template,Context,HttpServletResponse)
這個(gè)方法允許你產(chǎn)生輸出流。VelocityServlet提供了一個(gè)有效的Writer類的池,所以只有在特殊的情況下才重寫這個(gè)方法。
protected void error(HttpServletResquest,HttpServletResponse,Exception)
用于處理在處理請(qǐng)求過(guò)程中產(chǎn)生的異常。默認(rèn)的實(shí)現(xiàn)將發(fā)送一個(gè)包含錯(cuò)誤信息的簡(jiǎn)單HTML給用戶。可以重寫這個(gè)方法實(shí)現(xiàn)更高級(jí)的處理。
部署
當(dāng)你部署基于Velocity的Servlet時(shí),請(qǐng)確保你的屬性文件被用來(lái)配置Velocity運(yùn)行時(shí)刻環(huán)境。在Tomcat中,一個(gè)不錯(cuò)的方法就是放置你的velocity.properties文件在你的web應(yīng)用的根目錄下,并在web.xml文件中如下配置:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.foo.bar.MyServlet</servlet-class>
<init-param>
<param-name>properties</param-name>
<param-value>/velocity.properties</param-value>
</init-param>
</servlet>
|
Velocity在核心運(yùn)行時(shí)刻類中使用了單例模式,為了確保web應(yīng)用的classloader會(huì)管理你的運(yùn)行時(shí)刻實(shí)例,把velocity-xx.jar包放在WEB-INF/lib目錄下是一個(gè)好主意!不要放在CLASSPATH或容器的根路徑下。
(待續(xù))