Velocity開發(fā)者指南

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

一、Velocity 的工作原理

基本模式

當(dāng)我們在ApplicationServlet(實際上包括其他任何形式)中使用Velocity時,通常會做如下幾件事:

·初始化Velocity。適用于Velocity的兩種應(yīng)用模式-單例(Singleton)和(separate runtime instance),并且你僅需要執(zhí)行一次。

·創(chuàng)建一個Context對象。

·向Context對象添加數(shù)據(jù)。

·選擇一個模板。

·合并模板和數(shù)據(jù)并輸出。

通過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);

 

這是最基本的使用方式,非常簡單!但這恰恰就是當(dāng)你用Velocity表現(xiàn)一個模版的時候所發(fā)生的事情!事實上,你并不一定要嚴(yán)格的按照這種方式寫代碼,因為我們?yōu)?/SPAN>servletApplication開發(fā)人員提供了一些更加簡單的工具。

二、是否使用單例模式

Velocity1.2和以后的版本中,開發(fā)人員有了兩中使用Velocity引擎的方式:單例模式(singleton model)或分離實例模式(separate instance model)。兩種方式采用相同的核心Velocity代碼,這使得更加容易整和VelocityJava應(yīng)用程序。

單例模式

JVM或者web應(yīng)用中僅僅存在一個Velocity引擎的實例,并且所有應(yīng)用都共享它。這個實例允許本地化配置和資源共享,可以通過org.apache.velocity.app.Velocity類獲得這個單例,就象下面這樣:

 

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基類采用了單例模式,這是一個用于幫助開發(fā)servlets的實用類。盡管繼承這個類是應(yīng)用Velocity開發(fā)Servlets的最常用、最方便的方法,但是你仍然可以自由選擇是否使用這個類。

 

分離實例

作為1.2版的一個新特性,你可以在同一個JVMweb應(yīng)用中創(chuàng)建、配置和使用任意多的Velocity實例。通過org.apache.velocity.app.VelocityEngine類來使用分離的實例。就象下面這樣:

 

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");

 

 

正如你所看到的一樣,非常的簡單易懂!除了改變一些簡單的語法,在你的應(yīng)用中采用單例模式或分離實例模式不需要其他任何改變!

作為程序開發(fā)人員,你可以使用org.apache.velocity.app.Velocityorg.apache.velocity.app.VelocityEngine兩個類同Velocity內(nèi)部交互。但是,請記住,任何時候絕對不要在你的應(yīng)用程序中使用內(nèi)部的org.apache.velocity.runtime包中Runtime,RuntimeConstants,RuntimeSingleton或者RuntimeInstance類,因為它們僅僅是供內(nèi)部使用的,并且以后可能會改變!

 

三、上下文

基礎(chǔ)

上下文(context)是Velocity的核心概念,也是在系統(tǒng)各部分之間移動“數(shù)據(jù)容器”的一種常用技術(shù)。context作為Java層和模板層的數(shù)據(jù)載體!作為開發(fā)人員,你需要收集你的應(yīng)用程序所需要的各種類型的對象,并把它們放在context中。作為設(shè)計者,可以通過 引用 來存取這些對象。通常,開發(fā)人員需要和設(shè)計人員一同研究決定應(yīng)用中需要的數(shù)據(jù)。因此,這種協(xié)同開發(fā)值得多花費一些時間并仔細(xì)對需求進(jìn)行分析!

Velocity允許開發(fā)人員創(chuàng)建自己的context類來支持特殊需求或技術(shù)(LDAP server),并提供了一個基礎(chǔ)的實現(xiàn)類VelocityContextVelocityContext適用于所有通常的需求,強(qiáng)烈建議使用它!僅僅在特殊和高級的案例中創(chuàng)建自己的context實現(xiàn)。

使用VelocityContext就象使用HashTable一樣簡單,這個接口包含了許多有用的方法,最常用的是:

 

public Object put(String key,Object value);

public Object get(String key);

 

需要注意的是,如同HashTable一樣,值必須是Object類型的,并且不能為空!象 intfloat等基本數(shù)據(jù)類型必須包裝成適當(dāng)?shù)念愵愋汀?/SPAN>

這里是一些基本的context操作,更多信息請查看API

通過#foreach()對迭代對象的支持

作為開發(fā)人員,你可以把多種對象存儲到context中,但是也有一些限制,所以應(yīng)該理解Velocity支持什么類型,以及可能會產(chǎn)生什么問題。Velocity可以在VTL#foreach()方法中使用很多類型的集合。

·Object[]正常的對象數(shù)組。 Velocity會在內(nèi)部使用一個提供Iterator接口的類來包裝這個數(shù)組,但是開發(fā)人員和模板設(shè)計者不需要關(guān)系這個過程。

·java.util.Collection Velocity將調(diào)用iterator()方法獲得一個Iterator,所以如果你的類實現(xiàn)了一個Collection接口,請確信iterator()方法會返回一個可用的Iterator.

·java.util.Map 這里,Velocity將通過values()方法獲得一個Collection接口,并通過這個接口調(diào)用iterator()方法獲得Iterator

·java.util.Iterator 注意:這只是暫時被支持的,原因是Iterator不支持reset。如果一個Iterator被存儲在context中,并且被多個#foreach()方法調(diào)用,那么除第一個#foreach()方法外,其他的都將失敗,因為Iterator不支持reset!

·java.util.Enumeration 同上

基于IteratorEnumeration的限制,強(qiáng)烈建議僅僅在不可避免的時候才使用它們,并且如果可能的話,你應(yīng)該讓Velocity自己查找適當(dāng)?shù)目芍赜玫牡涌凇?/SPAN>

上下文鏈(Context Chaining

Velocity的一個創(chuàng)新的特性就是Context Chaining 概念。有時被稱為Context Wrapping,這個高級特性允許你把分離的contexts通過一種方式連接起來,對template而言就象是一個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連接起來。這意味著在模板中,你可以存取存放在VelocityContext中的任何對象,只要他們的key不重復(fù)就可以,如果key出現(xiàn)重復(fù)的話,那么context2中值將是可用的。但值得注意的是,實際上context1中重復(fù)的key并沒

有被改變或破壞,仍然可以通過context1.get(“duplicate”)方法獲得context1deplicate的值!但請記住:在模板中沒有任何方法獲得context1deplicate的值!同樣,當(dāng)你通過#set()方法向context中增加信息的時候,這個新的信息將被增加到最外層的context中的key中,所以,請不要試圖將信息通過#set()方法增加到內(nèi)層的context中!

Template中創(chuàng)建對象

通常有兩種情況需要在JAVA代碼中處理由template在運行時創(chuàng)建的對象:

·template調(diào)用由JAVA代碼放置到context中的對象的方法。

·JAVA代碼在合并后存取由template放置到context中的對象。

關(guān)于context的一些其他問題

VelocityContext的一個特性是結(jié)點特殊的內(nèi)省緩存(introspection caching)。通常,作為開發(fā)人員可以放心的把VelocityContext做為context使用。但是,必須知道關(guān)于這個特性的一個使用模式:

VelocityContext會堆積它訪問過的模板中的語法結(jié)點的內(nèi)省信息。所以在下述情況下:

·重復(fù)使用同一個VelocityContext迭代訪問同一個模板

·模板緩存關(guān)閉

·在每個反復(fù)迭代中調(diào)用getTemplate()方法請求獲得Template

VelocityContext可能會導(dǎo)致內(nèi)存泄漏(實際上是聚集了太多的內(nèi)省信息)。原因是VelocityContext 會堆積它所訪問過的每個模板的內(nèi)省信息,如果template緩存被關(guān)閉,將導(dǎo)致VelocityContext每次都訪問一個新的模板,從而堆積更多的內(nèi)省信息。

強(qiáng)烈建議你做如下的事情:

·當(dāng)template處理結(jié)束后創(chuàng)建一個新的VelocityContext,這將阻止堆積內(nèi)省信息。如果你需要重用攜帶數(shù)據(jù)和對象的VelocityContext,可以簡單的用另一個VelocityContext來包裝它。外層的VelocityContext將會堆積內(nèi)省的信息,但另人興奮的是你將丟棄它!

·開啟模板的緩存機(jī)制!這將阻止每次都對template重新解析,這樣VelocityContext不僅可以避免增加內(nèi)省信息,同時還可以改進(jìn)程序。

·在循環(huán)迭代期間重用Template對象。這樣,當(dāng)緩存關(guān)閉的時候就不用強(qiáng)迫Velocity一次又一次的去重新讀取和重新解析同樣的template,因此,VelocityContext也就不會每次都堆積新的內(nèi)省信息!

 

四、在Servlets中使用Velocity

Servlet編程

Velocity的一個主要的應(yīng)用領(lǐng)域就是JAVA Servlet。有很多理由可以說明Velocity適合這個領(lǐng)域,最關(guān)鍵的是Velocity強(qiáng)制視圖層(VIEW)和代碼分離!

servlet中使用velocity非常的簡單。基本上你要做的就是繼承VelocityServlet基類和實現(xiàn)handRequest()方法。

Velocity1.1開始,有兩個handRequest()方法:

 

public Template handRequest(Context)

這是舊的方法。這個方法要求返回一個有效的Template對象。如果無效或者為空,將會產(chǎn)生異常!同時error()方法會被調(diào)用!如果你希望產(chǎn)生異常后做一些其他的事情(比如重定向)可以重寫這個方法。強(qiáng)烈建議您使用新的方法!

public Template handRequest(HttpServletRequest,HttpServletResponse,Context)

這是新方法。與舊的方法不同,它可以返回一個null來說明方法已經(jīng)執(zhí)行,并且Velocity什么都不做。

下面是一個簡單的例子:

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;

    }

}

 

 

是不是覺得很面熟?除了創(chuàng)建context對象,這已經(jīng)由VelocityServelt幫你做了,并且VelocityServlet也幫你實現(xiàn)了merge()方法,這和我們開始部分的編碼方式基本上是一樣的。我們獲得context和應(yīng)用程序數(shù)據(jù),并反回一個template

默認(rèn)傳遞給handleRequest()方法的context對象包含當(dāng)前的HttpServletRequestHttpServletResponse對象。他們分別被放置在常量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)

這個方法允許重寫通常的配置機(jī)制,增加或修改配置屬性。

 

Context createContext(HttpServletRequest,HttpServletResponse)

這個方法允許你創(chuàng)建自己的Context對象。默認(rèn)的實現(xiàn)只是簡單的包含requestresponseVelocityContext對象。為了避免在一些servlet容器中發(fā)生內(nèi)省緩存問題,這個resquestresponse對象被一個簡單的包裹器類包裝了。你可以正常的使用他們,但是請注意他們并不是javax.servlet.XXX

 

void mergeTemplage(Template,Context,HttpServletResponse)

這個方法允許你產(chǎn)生輸出流。VelocityServlet提供了一個有效的Writer類的池,所以只有在特殊的情況下才重寫這個方法。

 

protected void error(HttpServletResquest,HttpServletResponse,Exception)

用于處理在處理請求過程中產(chǎn)生的異常。默認(rèn)的實現(xiàn)將發(fā)送一個包含錯誤信息的簡單HTML給用戶。可以重寫這個方法實現(xiàn)更高級的處理。

部署

當(dāng)你部署基于VelocityServlet時,請確保你的屬性文件被用來配置Velocity運行時刻環(huán)境。在Tomcat中,一個不錯的方法就是放置你的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在核心運行時刻類中使用了單例模式,為了確保web應(yīng)用的classloader會管理你的運行時刻實例,把velocity-xx.jar包放在WEB-INF/lib目錄下是一個好主意!不要放在CLASSPATH或容器的根路徑下。

(待續(xù))