Velocity開發(fā)者指南

(學(xué)習(xí)筆記)

一、Velocity 的工作原理

基本模式

當(dāng)我們?cè)?/SPAN>ApplicationServlet(實(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>servletApplication開發(fā)人員提供了一些更加簡(jiǎn)單的工具。

二、是否使用單例模式

Velocity1.2和以后的版本中,開發(fā)人員有了兩中使用Velocity引擎的方式:?jiǎn)卫J?/SPAN>(singleton model)或分離實(shí)例模式(separate instance model)。兩種方式采用相同的核心Velocity代碼,這使得更加容易整和VelocityJava應(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è)JVMweb應(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.Velocityorg.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)類VelocityContextVelocityContext適用于所有通常的需求,強(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類型的,并且不能為空!象 intfloat等基本數(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 同上

基于IteratorEnumeration的限制,強(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 );

在上面的例子中,我們把context2context1連接起來(lái)。這意味著在模板中,你可以存取存放在VelocityContext中的任何對(duì)象,只要他們的key不重復(fù)就可以,如果key出現(xiàn)重復(fù)的話,那么context2中值將是可用的。但值得注意的是,實(shí)際上context1中重復(fù)的key并沒(méi)

有被改變或破壞,仍然可以通過(guò)context1.get(“duplicate”)方法獲得context1deplicate的值!但請(qǐng)記住:在模板中沒(méi)有任何方法獲得context1deplicate的值!同樣,當(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)前的HttpServletRequestHttpServletResponse對(duì)象。他們分別被放置在常量VelocityServlet.REQUESTVelocityServlet.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)單的包含requestresponseVelocityContext對(duì)象。為了避免在一些servlet容器中發(fā)生內(nèi)省緩存問(wèn)題,這個(gè)resquestresponse對(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)你部署基于VelocityServlet時(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ù))