Velocity空间

快速构建JAVA应用
随笔 - 11, 文章 - 15, 评论 - 5, 引用 - 0
数据加载中……

CHAPTER 6 理解模板和上下文

在之前的章节里,我们介绍了VelocityHello World 示例,并从中看到了Velocity的一些简单应用,是时候学习Velocity核心组件了。本章将深入学习模板和上下文。后续章节将在本章知识的基础上,详细讨论Velocity的引用、指令和宏。

使用模板

Velocity是一个模板引擎,使用Velocity进行开发将是一件非常愉快的事。但是如何来制作模板呢?Velocity的模板定义和许多领域使用的模板概念相似,比如许多文本处理包为通用文档提供了文档处理的预定义包,如office备忘录等,这些都与Velocity模板的概念相当接近。Velocity俱乐部应用程序窗体(详见Figure 6.1)是常规模板的另一个例子。


Figure 6.1
An example of a general template.

Velocity俱乐部应用程序的内容包含了两个部分的内容:静态内容和动态内容。静态内容由标题、标签(如:First Name: Last Name: 等)、问题和全部的布局等组成;动态内容就是表单中的下划线部分,用于申请人填写个人信息。

普通模板定义(如Velocity俱乐部应用程序的模板)和真实的Velocity模板最大的不同之处在于,Velocity的动态内容是指定的。当应用程序窗体依赖于插入空白部分的动态内容时,Velocity使用了引用的概念。现在,你可以简单想一下,Velocity引用就好比某种实体引用了一个存储在别处的值。比如,基于Velocity引用的基本语法,一个模板或许包含了一个名叫$name的实体和字符串“John Doe”对应,该字符串一般存储在模板外的某个地方(如数据库)。如果你对这个含糊的定义有些困惑的话,请不要太担心,我们将在下章详细讨论引用的细节,重要的是要紧记Velocity引用从本质上来讲是作为模板中动态内容的占位符。如果你想把这个俱乐部应用程序转换成Velocity模板,只需要把那些空白部分替换成适当的Velocity引用就行。如何制定一个适当的Velocity引用定义?首先定义要符合Velocity引用语法,其次是设计者和编程人员要对模板引用的名称进行约定。一种可能的模式实现详见Listing 6.1

CLUB VELOCITY APPLICATION

First Name: $firstName

Last Name: $lastName

Address: $streetAddress

City: $city State: $state Zip: $zip

Phone Number: $phoneNumber

Email Address: $emailAddress

Occupation: $occupation

Other Interests: $otherInterests

Is this a new membership request or a renewal? $appType

How long have you been using Velocity? $useTime

Do you use Velocity for work or play? $useType

Do you want to receive our newsletter? $wantNewsletter

Listing 6.1 The club application form after conversion to a Velocity template.

正如你所看见的一样,Velocity模板从本质上讲,和应用程序原来的窗体是相同的,除了空白地方被Velocity引用替换外。下面我们先假定引用的值分别如下:

$firstName => "John"

$lastName => "Doe"

$streetAddress => "123 Jane Ave. "

$city => "Azusa"

$state => "CA"

$zip => "91702"

$phoneNumber => "626-555-1234"

$emailAddress => "john@nodom.com"

$occupation => "Web Developer"

$otherInterests => "Hiking,Biking"

$appType => "New"

$useTime => "6 months"

$useType => "Work"

$wantNewsletter => "Yes"

那么输出结果为(Listing 6.2):

CLUB VELOCITY APPLICATION

First Name: John

Last Name: Doe

Address: 123 Jane Ave.

City: Azusa State: CA Zip: 91702

Phone Number: 626-555-1234

Email Address: john@nodom.com

Occupation: Web Developer

Other Interests: Hiking,Biking

Is this a new membership request or a renewal? New

How long have you been using Velocity? 6 months

Do you use Velocity for work or play? Work

Do you want to receive our newsletter? Yes

Listing 6.2 Sample output for the Velocity template version of the club application form.

现在,你已经明白了如何从模板输出内容,让我们快速考虑一下在处理过程中如何防止模板的部分内容被输出。具有这样的处理能力不仅对你有用,对你的同事也有使用,一句注解或几句模板代码的说明能让你在阅读代码时更易理解你当时设计代码的意图。另外,也具有在调试session期间选择性地让部分模板内容停止输出的能力,有时还需要明确地停止Velocity的默认处理行为。Velocity支持模板注释机制提供了这种能力,它包括块注释和单行注释。块注释由#*开始,到 *#结束,所有在它们之间的内容将被模板引擎丢弃。单行注释由两个##开始的。Listing 6.3提供了一些模板注释示例。

之前的内容描述了Velocity的概貌,但我们忽视了几个重要的主题。首先,这将意味着Velocity引用比我们目前透露的还要多。此外,Velocity还提供了许多可直接控制模板内容的指令和宏。指令提供了流控制、文件包含和引用处理功能;宏为模板代码提供了一个强大的可再使用机制。我们将在随后的章节里讨论这些主题。

#*

This is a block comment. It is being used to point out

that this listing is intended to demonstrate the use of

comments in Velocity templates.

*#

##This line is not rendered.

This part is rendered,## but this part is not.

If only the #*middle*# bit needs to be commented out, a

block comment will do the job.

Listing 6.3 Velocity comment examples.

上下文

在前面的部分,我们为应用程序模板里的引用指定了字符串。然而,我们曾经提到过把Velocity引用和字符串值绑定在一起的机制,这就是Velocity上下文的概念。上下文在应用程序端表现为通过在org.apache.velocity.context中定义的上下文接口。上下文通过关键字映射存储的对象。上下文只存储java.lang.Object类型的对象,该对象的关键字类型为java.lang.String

引用除了具有$前缀外,用于关键字的字符串值和用于引用名称的值必须相同。例如,字符串值"John"在上下文里对应关键字firstName,模板将通过引用$firstName访问字符串值"John"。大体上,模板的动态内容通过关键字在上下文中查找内容来指定。$前缀让模板引擎知道$后的文本就是上下文中关键字的名称。

Velocity上下文和对象进行组装有三条途径。首先,Velocity自身可以向上下文中插入有效的值,比如使用#foreach指令进行反复计算。第二,#set指令允许模板向上下文中直接插入值。最后,更为重要的是,Velocity上下文接口允许编程人员使用双方约定的关键字名称,利用设计人员需要的数据来组装上下文。

我们只讲解最后一种情况。

在一个典型的应用程序里,上下文表现为一个org.apache.velocity.VelocityContext的实例,它实现了上下文接口。上下文实例存储了对象"John" "Doe",对应关键字分别为"first-Name" "lastName",代码如下:

VelocityContext context = new VelocityContext();

context.put( "firstName", "John" );

context.put( "lastName", "Doe" );

创建好后,模板就可以使用引用$firstName $lastName来访问对象"John""Doe"。在这种情况下,虽然上下文中的对象已经是JAVA字符串类型,但是它比其他的JAVA类型(比如Integer Float)更容易接受的。在其他情况下,Velocity使用对象的toString()方法来生成一个字符串,以用于显示输出。当然,它也可能是比简单值更复杂的一个对象,这通常发生在模板的需求包含了高级功能的情况下,这时只能通过对象的方法存储在上下文中。最后一种情况的细节将在Chapter 7讨论(Velocity引用)。

除了组装上下文外,上下文接口还允许编程人员对上下文进行查询和更深入的操作,提供的附加方法如下:

boolean containsKey( java.lang.Object key )

java.lang.Object[] getKeys()

java.lang.Object remove( java.lang.Object key )

java.lang.Object get( java.lang.String key )

containsKey()方法允许通过关键字检索上下文,返回true(表示找到了和关键字对应的对象)和falsegetKeys()方法返回所有关键字的列表,要注意的是返回的类型为对象数组,而不是字符串数组。remove()方法用于移除关键字对应的对象,同时也将移除关键字,返回值为被移除对象的值。get()方法允许编程人员通过关键字访问对象。

Putting the Pieces Together放置组合体?

既然你已经对模板和上下文在Velocity中扮演的角色有了大概了解,那就让我们把组合体放到应用程序的窗体里,对 Velocity模板进行处理,以显示俱乐部Velocity成员应用程序的窗体。既然本应用示例的真实意图是用于阐明Velocity应用的基础结构,那我们就让它的逻辑尽量简单。特别的,本示例中,动态组件的最终内容是来自硬编码的字符串,在现实的应用程序中,这些动态内容更多来自数据库或产生于别的途径。

在查看应用程序代码之前,先来了解大概的顺序。把Listing 6.1定义的模板作为起点,开发一个处理模板的应用程序,用Velocity上下文中适当的文本替换所有的引用,最终内容(包含直接取自模板的静态内容和取自上下文的动态内容)将被应用程序输出,结果详见Listing 6.2

符合这个目标的应用程序源代码详见Listing 6.4。这些代码是比较典型的通用代码,适用于多数 Velocity应用程序,它有完善的注释。它共有六个步骤:模板引擎初始化,模板包含,上下文创建,上下文组装,模板和上下文合并和内容表示。要注意的是,这仅仅是一个通用模式,它还有一些弹性,可以进行进一步扩展。但是,如果你牢牢记住了这个模式,就会少犯错误。

import java.io.StringWriter;

import org.apache.velocity.Template;

import org.apache.velocity.VelocityContext;

import org.apache.velocity.app.Velocity;

import org.apache.velocity.exception.*;

public class ClubApp

{

public static void main( String[] args )

{

// Initialize template engine

try

{

Velocity.init();

}

catch( Exception x )

{

System.err.println( "Failed to initialize Velocity: " + x );

System.exit( 1 );

}

// 获取模板

Template clubTemplate = null;

try

{

clubTemplate = Velocity.getTemplate( "ClubApp.vm" );

}

catch( ResourceNotFoundException rnfX )

{

System.err.println( "Template not found: " + rnfX );

System.exit( 1 );

}

catch( ParseErrorException peX )

{

System.err.println( "Failed to parse template: " + peX );

System.exit( 1 );

}

catch( Exception x )

{

System.err.println( "Failed to initialize template: " + x );

System.exit( 1 );

}

// 创建上下文

VelocityContext context = new VelocityContext();

// Populate context

context.put( "firstName", "John" );

context.put( "lastName", "Doe" );

context.put( "streetAddress", "123 Jane Ave." );

context.put( "city", "Azusa" );

context.put( "state", "CA" );

context.put( "zip", "91702" );

context.put( "phoneNumber", "626-555-1234" );

context.put( "emailAddress", "john@nodom.com" );

context.put( "occupation", "Web Developer" );

context.put( "otherInterests", "Hiking,Biking" );

context.put( "appType", "New" );

context.put( "useTime", "6 months" );

context.put( "useType", "Work" );

context.put( "wantNewsletter", "Yes" );

// Merge template and context

StringWriter writer = new StringWriter();

try

{clubTemplate.merge( context, writer );}

catch( ResourceNotFoundException rnfX )

{

System.err.println( "Template not found on merge: " + rnfX );

System.exit( 1 );

}

catch( ParseErrorException peX )

{

System.err.println( "Failed to parse template on merge: " + peX );

System.exit( 1 );

}

catch( MethodInvocationException miX )

{

System.err.println( "Application method exception: " + miX );

System.exit( 1 );

}

catch( Exception x )

{

System.err.println( "Failed to merge template: " + x );

System.exit( 1 );

}

// Render merged content

System.out.println( writer.toString() );

}

}

Listing 6.4 An application for processing the club membership form.

我们现在来讨论一下这个示例,可以说是把它放到放大镜下进行观察。代码开始于import语句,你可以看到java.io.StringWriter被导入到程序中,虽然StringWriter类并非必须,但一般情况下,导入StringWriter类是适当的,因为模板和上下文的合并处理通常需要一个java.io.Writer的衍生对象或衍生类。导入的第一个Velocity包是org.apache.velocity.Template,其相应的模板类提供了一个由应用程序使用的、常驻内存的模板。接着导入的是org.apache.velocity.VelocityContext类,用于处理Velocity上下文。之后,应用程序导入了org.apache.velocity.app.Velocity类,它提供了独立模式的Velocity模板引擎。通过org.apache.velocity.app.VelocityEngine类(将在Chapter 10讨论)可实现非独立模式Velocity模板引擎(non-Singleton implementation)。最后导入的是org.apache.velocity.exception.*,它提供了一个访问各种Velocity异常类的能力。

Import语句之后,将进入模板处理的执行代码。正如前面提及的,紧随其后的是Velocity应用的通用代码。第一个元素是初始化模板引擎,使用的是独立模式,初始化操作非常容易,仅仅调用静态方法Velocity.init()即可,如果因其他原因造成初始化失败,将抛出一个java.lang.Exception类型的通用异常;如果使用的是非独立模式,你需要创建VelocityEngine的实例来代替它,并调用该实例的init()方法,而不是直接调用Velocity的静态方法Velocity.init()

初始化模板引擎之后,应用程序下一步将获取一个常驻内存的模板。随着模板引擎的初始化,这一步完成时创建的manner(依赖于独立或非独立模式)就可以使用了。在独立模式下,通过调用Velocity类的静态方法getTemplate(),将返回一个模板的实例。这个方法通过模板名称参数获得并加载了一个模板文件,本例中是ClubApp.vm。注意,虽然VM是公认的Velocity模板文件标准后缀,你也不必遵守这个约定,你可以将其改成其他的后缀,比如:XXX

如果一个非独立模式被替换用于模板获取,剩余的处理必须相同。关键差别在于调用getTemplate()方法,非独立模式只能用VelocityEngine创建的实例来调用,不能采用调用Velocity静态方法的方式来调用。不管采用哪种模式,都必须增加相同的异常处理集,包括ResourceNotFoundException ParseErrorExceptionException。前两个Velocity异常在org.apache.velocity.exception包里指定,最后一个是标准的java.lang. Exception包。ResourceNotFoundException异常在Velocity不能定位指定模板文件时抛出;ParseErrorException异常在Velocity不能解析模板时抛出;最后,Exception异常在模板获取期间,发生任何其他问题时抛出。

在正确获取模板之后,你就可以创建和组装上下文了。首先要创建一个VelocityContext实例。上下文的组装通过实例调用VelocityContextput()方法来完成,每一个关键字对应一个JAVA对象。在一个典型的Velocity应用中,很多处理都将围绕上下文组装这个操作,这里是生成让模板设计者可用的动态内容的地方,所有的数据中转和处理都将在这里完成。

现在你手里已经有了模板和上下文,下一步就是合并他们。这一步的作用是用从上下文中获取的数据替换模板中的引用,该合并操作是通过调用模板实例(之前创建的)的merge()方法完成的。模板的merge()方法需要两个参数,第一个是上下文(实现了上下文接口的任何对象),这里,你需要传递一个Velocity-Context的实例;第二个是java.io.Writer类型的对象,或其他Writer的衍生类型对象。Writer对象用于保存模板处理的结果。

最后,将输出最后的处理结果。一旦你把StringWriter用于合并操作,render操作将不再调用WritertoString()方法,而是直接输出到适当的位置,比如浏览器。输出结果见Listing 6.2

本章小节和下章介绍

这一章全面介绍了Velocity模板和上下文基础,我们通过一个示例应用示范了如何使用模板和上下文,并且对他们进行了详细的讨论。通过这些内容的学习,你应该可以设计一些基本的模板或开发简单的模板处理应用程序了。然而,在使用Velocity进行更多高级模板处理任务时,仍有许多障碍,要清除这些障碍,我们首先需要对Velocity引用进行更广泛的研究,这就是我们下一章将要学习的内容。

posted on 2008-10-14 08:52 KINGWEE 阅读(812) 评论(1)  编辑  收藏 所属分类: Velocity

评论

# re: CHAPTER 6 理解模板和上下文  回复  更多评论   

很细致,写得很好
2012-08-13 16:38 | 各个

只有注册用户登录后才能发表评论。


网站导航: