在之前的章节里,我们讨论了Velocity模板语言的核心组件和Velocity上下文在模板处理中扮演的角色。这些章节提供了设计需要的相关信息和基于模板的应用程序实现。许多附加的Velocity特性让你真正能够获得模板处理的控制权。在这一章里,我们将介绍这些特性,包含运行时配置、事件、空白符(whitespace)管理和上下文链(chaining)。
初始化运行时配置
在Chapter 9讨论Velocimacros的时候,我们顺便介绍了许多影响模板引擎处理Velocimacros方式的Velocity属性。在这一章里,我们将继续介绍更多的影响运行时系统的Velocity属性,首先我们来讨论这样的属性在是如何定义和传递给Velocity运行时的。
在之前的示例里,我们通过简单调用静态方法Velocity.init()来初始化运行时。Velocity运行时引擎用默认的Velocity属性(在org/apache/velocity/runtime/defaults/velocity.properties描述文件里指定)来进行初始化。这些默认属性提供了一个适合许多场合的合理配置。为了满足更多的模板引擎行为控制方式,Velocity提供了三种技术来定制运行时配置。不管使用哪种技术,运行时总是把velocity. Properties文件作为启动配置文件。因此,只需把需要的属性进行调整或把新的属性加入到该文件中即可。
第一个用于定制Velocity运行时配置的技术是用于默认配置的镜像技术。步骤是使用和velocity.properties文件一样的语法来一个运行时配置文件。使用这个语法,你需要通过提供属性名称和属性值(属性名称=属性值)来设置一个Velocity属性,如果属性有多个值时,在值之间用逗号进行分隔。至于定制技术,在这个文件里的属性不是重写就是增加新的到默认配置里。该配置文件通过调用Velocity引擎init()方法(以文件名作为参数)的重载版本来传递给运行时。比如,假如你正在测试某些在tags.vm和labels.vm文件里定义的新的Velocimacros。你肯定愿意使他们的库对运行时可见(除了在模板文件里重载宏的情况),并且允许Velocimacros库能够自动重新加载以便于调试。到最后,你创建了一个名叫custom.properties的文件(见Listing 10.1)。该文件指定的属性之后通过给Velocity引擎的初始化方法提供该文件名称来传递给运行时(见Listing 10.2)。
## Specify the names of our custom libraries
velocimacro.library = tags.vm, labels.vm
## Disable inline Velocimacro definitions
velocimacro.permissions.allow.inline = false
## Enable Velocimacro library auto-reloading
velocimacro.library.autoreload = true
Listing 10.1 A custom Velocity runtime configuration file.
// Initialize template engine
try
{
Velocity.init( "custom.properties" );
}
catch( Exception x )
{
System.err.println( "Failed to initialize Velocity: " + x );
System.exit( 1 );
}
Listing 10.2 An example of runtime initialization using a custom configuration file.
如果为一个应用程序提供更多Velocity属性规范的控制权比较重要的话,或者如果提供便捷读写属性的能力比较重要的话,我们接下来的讨论将非常有意义。这个技术包括java.util.Properties对象的使用。如果在运行时里构建属性对象,则对象的setProperty()方法将用于为Velocity属性增加实体。这个方法需要两个字符串类型的参数:即属性关键字和属性值。Velocity属性名称用作关键字,并且Velocity属性的值(在字符串窗体里)是用于值参数的。注意,我们在这里是用term property来描述两个不同的实体。相对于Java属性对象,它引用的是与Velocity没有固定关系的普通属性。在所有其他情况下,我们谈到用于定义Velocity模板引擎的运行时配置的属性。(If it is important to provide an application with more control over the specification of Velocity properties, or if the ability to easily read and write the properties is desirable, the technique we discuss next might be preferable. This technique involves the use of a java.util.Properties object. If building up the Properties object at runtime, the object’s setProperty() method is used to add entries for Velocity properties. This method expects two parameters of type String: the property key and the property value. The Velocity property name is used as the key, and the value of the Velocity property--in the form of a string literal--is used for the value argument. Note that we use the term property here to describe two distinct entities. Relative to the Java Properties object, it refers to
a generic property with no inherent relationship to Velocity. In all other cases, we are referring to properties that define the runtime configuration of the Velocity template engine.)
如果我们修改的模板引擎初始化为Listing 10.2,那么,它使用了一Java属性对象来配置这个运行时和Listing 10.1文件指定的是相同的,代码看起来和Listing 10.3差不多。注意,这些属性对象仍然通过其他重载版本的init()方法来传递给Velocity引擎。你可以利用Java属性类提供的load()和store()方法来获得简单、有效读写配置文件的能力。这个方法相比Velocity基于文件的配置(直接在该文件里修改或增加属性)的优势在于,只需要写书少量的代码和该文件使用的是Java格式。
Properties customProps = new Properties();
// Specify the names of our custom libraries
customProps.setProperty( "velocimacro.library", "tags.vm, labels.vm" );
// Disable inline Velocimacro definitions
customProps.setProperty( "velocimacro.permissions.allow.inline", "false" );
// Enable Velocimacro library auto-reloading
customProps.setProperty( "velocimacro.library.autoreload", "true" );
// Initialize template engine
try
{
Velocity.init( customProps );
}
catch( Exception x )
{
System.err.println( "Failed to initialize Velocity: " + x );
System.exit( 1 );
}
Listing 10.3 An example of runtime initialization using a Java Properties object.
最后,如果定制运行时配置的主要目的是基于应用程序运行时条件细粒度地(fine-grain)控制Velocity属性,那么,最后一种技术或许是最后的选择。这个技术完全基于Velocity引擎的setProperty()方法,这个方法与属性(Properties)类的setProperty()方法(我们讨论的最后一种技术)无关,虽然他们使用了相似的方式。模板引擎的setProperty()方法需要两个参数:一个字符串定义的属性名称和一个用于表示相应值的对象。getProperty()方法可用于查询当前的属性设置值。在需要完全移除Velocity属性的情况下,甚至还可以使用clearProperty()方法。一旦所有相应的Velocity属性设置好后,通过调用Velocity引擎的无参数init()方法,即可对运行时进行配置。接下来的示例演示了上面两种技术,Listing 10.4包含一个使用Velocity setProperty()方法的版本。作为另外一种技术,这个setProperty()方法和基于默认配置定义文件velocity.properties相接近。
// Specify the names of our custom libraries
Velocity.setProperty( "velocimacro.library", "tags.vm, labels.vm" );
// Disable inline Velocimacro definitions
Velocity.setProperty( "velocimacro.permissions.allow.inline", "false" );
// Enable Velocimacro library auto-reloading
Velocity.setProperty( "velocimacro.library.autoreload", "true" );
// Initialize template engine
try
{
Velocity.init();
}
catch( Exception x )
{
System.err.println( "Failed to initialize Velocity: " + x );
System.exit( 1 );
}
Listing 10.4 An example of runtime initialization using the Velocity engine's setProperty() method.
无论你使用什么样的技术,在任何窗体里调用引擎的init()方法之前,一定要确保已经完成了所有的Velocity属性修改和增加操作。多次调用init()方法对运行时配置并没有影响,一旦完成第一次调用,引擎将把本次调用的运行时配置作为工作配置。
更多的Velocity属性
既然我们已经熟悉了Velocity的运行时属性配置的各种途径,那就让我们来仔细了解一下这些属性。我们把这些属性分成5个类型来进行讨论:指令、译码(encoding)、日志(logging)、资源管理和其他。第6个类别:Velocimacros已经在Chapter 9里进行了讨论。
指令属性
下面的指令将影响某几个Velocity指令的行为。
directive.foreach.counter.name
directive.foreach.counter.name属性用于指定VTL标识符,用于#foreach指令的循环计数器名称。当使用了前缀$时,这个标识符用作Velocity变量引用,它允许模板设计者访问#foreach指令的当前反复数(即循环数)。默认情况下,这个计数器从1开始,且每一次循环的增量为1。directive. foreach.counter.name属性的值默认为velocityCount,其相应的变量引用为$velocityCount。如果模板设计者想用引用名称$my-Count来代替这个值,则可以为该属性赋值为myCount。
directive.foreach.counter.initial.value
directive.foreach.counter.initial.value属性指定了#foreach指令的循环计数器初始值,这个值通过循环计数器引用提供(见directive.foreach.counter.name属性),作为每一个后来反复的起点,这个值将增加一个步长(At the beginning of each subsequent iteration, the value is incremented by one)。directive.foreach.counter.initial.value的默认值是1。熟悉的C++和Java loop循环的模板设计者或许更喜欢基于0的计数器,那么你把这个属性设置为0即可。
directive.include.output.errormsg.start
directive.include.output.errormsg.start属性指定了在(由非法输入参数被传递到#include指令而产生的)错误信息之前的文本。比如错误输入了一个未定义或不明确的(undefined)Velocity引用,将触发这种错误信息。这个错误信息前缀通过定义这个属性来指定。其默认值为“<!-- include error :”。
directive.include.output.errormsg.end
directive.include.output.errormsg.end属性指定了跟随在(由非法输入参数被传递到#include指令而产生的)错误信息之后的文本。比如错误输入了一个未定义或不明确的(undefined)Velocity引用,将触发这种错误信息。这个错误信息后缀通过定义这个属性来指定。其默认值为“see error log -->”。
directive.parse.max.depth
directive.parse.max.depth属性指定了#parse指令可以嵌套的最大深度。当值为1时,从本质上已经禁用了#parse指令,当一个模板包含了#parse指令时,深度的值就已经为1了,所以该值至少为1。虽然该属性的主要目的是为了防止递归失控,但该深度限度也适用于普通不含递归的#parse嵌套。其默认值为10。
Encoding
下面的属性用于指定模板和数据的编码(The following properties specify encodings to be associated with templates and data used by certain tools associated with the Velocity template engine)。
input.encoding
input.encoding属性被用于指定模板引擎进行模板处理的编码。一旦设定好后,所有的模板输入都将转换为指定的编码。默认值为ISO-8859-1。所支持的编码依赖于Java字符集。
output.encoding
output.encoding属性被用于指定输出流的编码。这个不是多种用途的Velocity属性,通常情况下,仅供VelocityServlet类和Anakia项目使用。普通情况下,在Writer被合并之前,编码可以在Writer类实例的初始化时直接指定。其默认值为ISO-8859-1。
Logging
下面的属性将影响Velocity日志系统的行为。
runtime.log
runtime.log属性用于指定Velocity日志文件的路径。默认情况下,这个路径被指定为应用程序的相对路径,这个设置可以通过file.resource.loader.path属性(下面将要讨论的)定义来修改。runtime.log属性的默认值是velocity.log。通过前面的示例,对这个文件你应该比较熟悉了,每一次运行Velocity应用程序里,就会产生这个文件。
runtime.log.logsystem
runtime.log.logsystem属性定义了一个Velocity将要传递(hand off/手传手?)日志任务的对象,用于Velocity。为了使用这个对象,你需要有一个实现org.apache.velocity.runtime. log.LogSystem接口的类。这个属性主要用于当Velocity的日志需要与定制应用程序日志类结合的情况下。该属性没有默认值,同时要注意,该属性值最好是一个对象(当然也可以用字符串),它不可以被直接指定为配置文件或Java属性对象。
runtime.log.logsystem.class
runtime.log.logsystem.class属性用于指定运行时实例化Velocity日志服务的类。该属性的值可以由逗号分隔的类名称列表组成。运行时引擎按列表里的名称次序依次查找匹配的类。第一个匹配的类将用于Velocity日志的实例化。默认值为“org.apache.velocity. runtime.log.AvalonLogSystem, org.apache.velocity.runtime.log.SimpleLog4J logSystem”。日志功能也可以通过把该属性设置为org.apache.velocity. runtime.log.NullLogSystem而禁用。
runtime.log.error.stacktrace
runtime.log.error.stacktrace属性用于指定当Velocity运行时引擎记录错误日志时,是否允许产生和日志堆栈跟踪信息(既是否产生堆栈的跟踪日志信息)。虽然它自己支持这个功能,但其关联的功能仍旧没有实现(Although support for the property itself exists, the associated functionality is not yet implemented)。默认值为false。
runtime.log.warn.stacktrace
runtime.log.warn.stacktrace属性用于指定当Velocity运行时引擎日志一个警告时,是否允许产生和日志堆栈跟踪信息。虽然它自己支持这个功能,但其关联的功能仍旧没有实现(Although support for the property itself exists, the associated functionality is not yet implemented)。默认值为false。
runtime.log.info.stacktrace
runtime.log.info.stacktrace属性用于指定当Velocity运行时引擎日志一个报告信息时,是否允许产生和日志堆栈跟踪信息。虽然它自己支持这个功能,但其关联的功能仍旧没有实现(Although support for the property itself exists, the associated functionality is not yet implemented)。默认值为false。
runtime.log.invalid.references
runtime.log.invalid.references属性用于指定是否要对模板里的非法Velocity引用错误进行日志。如果值为true,非法引用将产生一个警告信息。如果值为false,将忽略非法引用的信息。默认值为true。
Resource Management
下面的属性将影响Velocity资源管理系统的行为。
resource.manager.class
resource.manager.class属性用于指定处理Velocity资源管理任务的类实例。这个类必须是已经实现了org.apache.velocity.runtime.resource.ResourceManager接口的类。默认值是org.apache.velocity.runtime.resource.ResourceManagerImpl。
resource.manager.cache.class
resource.manager.cache.class属性用于指定(代表资源管理)处理资源缓存请求的类实例,这个类必须是已经实现了org.apache.velocity.runtime.resource.ResourceCache接口的类。默认值是org.apache.velocity.runtime.resource. ResourceCacheImpl。
resource.manager.logwhenfound
resource.manager.logwhenfound属性用于指定当资源管理定位一个得到的资源时是否对相关信息进行日志。默认值是true,即允许对这样的信息进行日志。
resource.loader
resource.loader属性用于指定资源加载器的详细名称,为了通过Velocity属性定义资源加载器的行为,这个名称只用作标签(The resource.loader property associates a name with a particular resource loader. This name is used only as a label to further define the resource loader’s behavior via Velocity properties)。
紧随其后的部分,我们用字符串<loader>提交这些名称。在velocity.properties文件只定义了一个名叫file的资源加载器,在这里,我们把其相应的属性名称用<loader>来列出(替换了file)(In the following subsections, we use the string <loader> when referring to this name. The velocity.properties file defines only one resource loader, which it names file, and the corresponding property names are those listed in the following subsections with <loader> replaced by file)。我们将在稍后详细讨论资源加载的更多细节。
<loader>.resource.loader.description
resource.loader.description属性指定了一个文本的资源加载器描述符。这个属性只是定义一个报告信息,实现上它不会影响资源加载器的功能。velocity.properties文件为该属性提供了一个Velocity File Resource Loader的值。
<loader>.resource.loader.class
resource.loader.class属性用于指定加载关联资源类型的类。该类继承自Velocity的org.apache.velocity. runtime.resource.loader.ResourceLoader类,用于提供特定的资源类型。velocity.properties文件为file.resource.loader.class属性提供了一个org.apache.velocity.runtime.resource.loader.FileResourceLoader的值。
<loader>.resource.loader.path
resource.loader.path属性用于指定一个关联类型资源的根目录。为资源提供的任何位置都是相对于这个根目录的。velocity.properties文件为file.resource.loader.path属性提供了一个值:“.”,它确定了应用程序的根目录(这个根目录又相对于模板、日志文件资源等)。
<loader>.resource.loader.cache
resource.loader.cache属性用于指定是否允许在加载时缓存某几个资源。velocity.properties文件为file.resource.loader.cache属性提供了一个值:false。意思是防止从缓存模板加载文件资源,在开发和调试阶段是首选值。在生产阶段,值true是最好的选择。
<loader>.resource.loader.modificationCheckInterval
resource.loader.modificationCheckInterval属性用于指定缓存资源的修改信息检测时间间隔,用秒计算。这个属性仅仅在其相应的resource.loader.cache属性为true时才有意义。当属性质为负数时,将完全禁止检测功能。velocity.properties文件为file.resource.loader.modificationCheckInterval属性提供了一个值:2。
Miscellaneous
下面的属性将影响Velocity运行时行为的混杂样子(miscellaneous aspects)。
runtime.interpolate.string.literals
runtime.interpolate.string.literals属性用于指定是否允许模板引擎窜改字符串。受到影响的包括#set指令等号(=)右边的用双引号引起来的字符串、引用方法的参数、Velocimacro参数和其他Velocity指令的普通参数。如果这个属性的值为false,这样的字符串将被当成单引号引起来的字符串对待,从不被窜改。默认值为true。
parser.pool.size
parser.pool.size属性用于指定运行时启动时创建的解析器池大小。这个属性设置的是最小值,如果需要增加额外的解析器,并超过了设定值,则系统会自动创建,但这些新创建的不会增加到池中,用完即扔。默认值为20。
Resource Loaders
在讨论Velocity属性的时候,我们介绍了许多可能影响资源加载的属性,而不需要真正为资源加载定义什么。简而言之(Here we rectify that omission),Velocity resource可以简单理解为模板引擎的输入(omission. A Velocity resource is simply an input to the template engine)。这样的输入包括正规模板、Velocimacro库和#include指令导入的纯文本。
一个resource loader就是一个简单的实体,主要用于从特定的源里获取这样的资源。迄今为止,所有的示例都依赖Velocity的文件资源加载器,它通过FileResourceLoader类来实现。同样地,其默认属性在我们讨论Velocity资源管理的文件资源加载器配置的时候就已经研究过了。然而,除了创建定制的资源加载器之外,Velocity也完全支持其他三种类型的资源加载器:JAR、Classpath和DataSource。
JAR资源加载器是通过Velocity的JarResourceLoader类来实现的,用于从JAR文件获取资源。我们描述的这个文件资源加载器属性可用于JAR资源加载,除了可用于外,resource.loader.path属性使用带有异常的JAR URL语法加载JAR资源,关于这个语法的更多信息,见Java的JarURLConnection类文档。
Classpath资源加载器是通过Velocity的ClasspathResource-Loader类实现的,通过CLASSPATH资源里的ClassLoader来获取资源。这个资源可以是zip文件、JAR文件或目录。在和servlets一起工作时,这个资源加载器特别有用。资源加载器属性相关的加载器是resource.loader.description和resource.loader.class,但只有resource.loader.class是必需的。
DataSource资源加载器是通过Velocity的DataSourceResourceLoader类实现的。通过从Java DataSource对象获得的物理数据源连接来获取资源。一个明显的例子就是从一个关系数据库里获得Velocity模板和相关的资源的情况。这个资源加载器除了resource.loader.path以外,可以使用所有的资源加载器属性。这个加载器还唯一支持几个其他的属性。关于这个属性的更多相关信息,可查阅Velocity的API文档DataSourceResourceLoader类。这个资源加载器需要J2EE,还不能被标准Velocity构建包包含。
事件(Events)
为了给模板处理提供好的控制,Velocity在事件处理级支持有限的用户干涉。这里有三种类型的事件允许用户干预处理过程。第一种是通过#set指令尝试给Velocity引用分派null值。第二种当一个Java方法被调用时(是通过Velocity方法或属性引用)抛出异常。第三种是在每次把符合Velocity引用的值插入到输出流中。Velocity为以上三种类型提供了事件处理接口,接口名称分别为NullSetEventHandler、MethodExceptionEventHandler和ReferenceInsertionEventHandler。
通过NullSetEventHandler接口指定的处理方法用于传递描述#set指令等号(=)的左边或右边的字符串,该方法将返回一个预期的布尔值,用于标识事件是否需要日志。通过MethodExceptionEventHandler接口指定的处理方法用于传递一个描述这个抛出方法的类的类对象,该方法将一个字符串用于描述抛出方法的名称,并且抛出异常。这个方法将返回一个替换将被返回的值的对象,它拥有不抛出异常的Velocity方法或引用(The method returns an object that replaces the value that would have been returned had the Velocity method or reference not thrown an exception)。
通过ReferenceInsertionEventHandler接口指定的处理方法用于传递一个描述将要被处理的引用名称字符串和一个描述它的值的对象。该方法返回的对象将用它的toString()方法被插入到输出流中。
这里有一个Velocity事件处理的示例,考虑一下Listing 10.5的Java类定义。这个类定义了三个方法用于示范Velocity的事件处理特性。第一个用#set指令故意返回一个null值,第二个抛出了一个普通异常,第三个为输出流插入提供一个默认值。
public class EventGen
{
public String getNull()
{
return (null);
}
public void throwException() throws Exception
{
throw new Exception();
}
public String toString()
{
return "toString() handled by EventGen";
}
}
Listing 10.5 A Java class that assists in the creation of Velocity events.
现在,让我们来考虑一下Listing 10.6的模板和Listing 10.7的事件处理类。通过为#set指令分派null,模板可以触发所有三种用户干预型事件。调用一个抛出异常的方法,并且插入引用的值到输出流中。这个事件处理类定义允许用户干涉三种事件中的一种的方法。Null的分派导致了一个报告信息和no log entry。这个异常导致一个报告信息被插入到输出流中,并且引用插入事件导致$eventGen引用的值被重载(Next consider the template in Listing 10.6 and the event-handling class in Listing 10.7. The template triggers all three events for which user intervention is supported by assigning a null in a #set directive, invoking a method that throws an exception, and inserting the value of a reference into the output stream. The event-handling class defines methods that allow user intervention for each of the three events. The null assignment results in an informational message and no log entry. The exception results in an informational message being inserted into the output stream, and the reference insertion event results in the value of the $eventGen reference being overridden.
)。
## Trigger a NullSetEventHandler response
#set( $ref = $eventGen.getNull() )
## Trigger a MethodExceptionEventHandler response
$eventGen.throwException()
## Trigger a ReferenceInsertionEventHandler response
$eventGen
Listing 10.6 A template that generates all three of the Velocity events that allow user intervention.
import org.apache.velocity.app.event.NullSetEventHandler;
import org.apache.velocity.app.event.MethodExceptionEventHandler;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
public class EventHan implements NullSetEventHandler,
MethodExceptionEventHandler,
ReferenceInsertionEventHandler
{
// NullSetEventHandler method
public boolean shouldLogOnNullSet( String lhs, String rhs )
{
System.out.println( "From app: choosing not to log "
+ lhs + " = " + rhs );
return (false);
}
// MethodExceptionEventHandler method
public Object methodException( Class claz,
String method, Exception e )
{
String msg = e + " thrown by " + claz + ":" + method;
return (msg);
}
// ReferenceInsertionEventHandler method
public Object referenceInsert( String reference, Object value )
{
Object insertValue = value;
if ( reference.equals( "$eventGen" ) )
{
insertValue = "toString() handled by EventHan";
}
return (insertValue);
}
}
Listing 10.7 A Velocity event-handling class that handles all three of the Velocity events that allow user intervention.
如果你想干预事件的处理,把所有剩余的都通知Velocity运行时。那么你可以通过Velocity的事件-投掷(event-cartridge)类来完成。完成这最后一步的代码见Listing 10.8,结果见Listing 10.9。
// Create context
VelocityContext context = new VelocityContext();
// Populate context
context.put( "eventGen", new EventGen() );
// Setup event handler
EventCartridge eventCart = new EventCartridge();
eventCart.addEventHandler( new EventHan() );
eventCart.attachToContext( context );
// Merge template and context
Listing 10.8 Java code demonstrating event-handler registration and context attachment using Velocity's EventCartridge class.
From app: choosing not to log $ref = $eventGen.getNull()
java.lang.Exception thrown by class EventGen:throwException
toString() handled by EventHan
Listing 10.9 Results from processing the template in Listing 10.6.
上下文链(Chaining)
我们迄今为止探索的所有示例都用的是简单的Velocity上下文。对于很多的任务来说这已经足够了,当然,我们也可在一个单一的应用程序里使用多个上下文。在一个应用程序里使用多个上下文和使用一个上下文没什么两样,其创建和使用过程完全一样。然而,Velocity为使用多个上下文提供了便利的方式,那就是Velocity提供的上下文链(context chaining),通过它可以方便的使用上下文集合。
上下文链(context chaining)是一种封装多个上下文的有效技术,它把所有原始上下文里的对象(实体和关键字没有进行复制)插入到封装后的上下文中,这些对象对上下文集合体来讲是可用的。另一方面,如果这些原始上下文的实体(带有关键字)已经进行了复制后才进行封装,那么这些原始实体将不可见,虽然这些实体对上下文集合体(aggregate context)来讲是不可见的,但他们仍然可被原始上下文访问。
上下文链是通过重载VelocityContext构造器来实现的,它把其他VelocityContext对象作为输入参数,这个对象将被新创建的上下文封装。这样的链经常用作共享工具和数据层(data layering)。在第一情况下,一个单独的上下文由许多工具或普通帮助类组装而成。这个上下文之后被用于重载Velocity上下文构造器以创建附加的上下文,其结果是每一个新的上下文包含了工具集,而不需要为每一个新的上下文一个接一个手动插入工具。
在作为数据层的情况下,一个原始的上下文和核心数据一起被创建和组装。之后,这个上下文将用于重载VelocityContext构造器,以便创建新的上下文。这个新创建的上下文将和附加的数据一起被组装,如果适当的话,仍将用于创建另外的上下文。如果需要的话,这个过程将被重复很多次。这个技术常用于多少独立数据集打包成数据总集的情况,在这种情况下,最后一个单独的数据集可能需要在比它早一点的集合里重载数据。这个技术也同样用于(当一个核心数据集将保持相同的across多个模板,在每一次单独的模板被处理时,minor enhancements可能是必需要的)的情况(In the case of data layering, an initial context is created and populated with core data. Then this context is used with the overloaded VelocityContext constructor to create a new context. The new context is populated with additional data and, if appropriate, used to create yet another context. This process is repeated as many times as is necessary. This technique is useful for cases where an aggregate data set is built from individual sets, where data in a later set may need to override data in an earlier set. It is also useful for cases where a core data set will remain the same across multiple templates but minor enhancements may be required for each individual template processed.
)。
管理空白符(Whitespace)
当提到模板设计时,模板易读性的目的和希望输出的格式通常不一致。许多人趋向于用空白符(whitespace),例如缩排和空白行,其目的为是了改善源代码的可读性。很自然的,我们会用这种方式来处理模板代码,尤其是在使用了指令的情况下更是如此。不幸的是,在模板里空白符扮演了一个倍受批评的角色。和普通源代码中的大量空白符被忽略相反,模板引擎在处理模板源代码中的空白符时,不会自动忽略不需要的空白符。希望模板引擎能够自己决定哪些空白符是重要的,哪些是不需要的。
为了对这个问题有一个更直观的了解,让我们考虑一下Listing 10.10里的模板,这个模板使用不同的Velocity指令打印输出许多颜色的名称。我们希望的输出结果为Listing 10.11,但实际输出结果却是Listing 10.12所示。这个差异来自为了改进易读性。空白符(whitespace)由缩排和额外的回车进入了模板输出里产生的。
#macro( write $list )
#foreach( $color in $list )
$color
#end
#end
red
green
#set( $favorite = "blue" )
$favorite
#if ( $likeViolet )
violet
#else
purple
#end
#write( ["orange","yellow","brown"] )
Listing 10.10 A template that demonstrates the effects of whitespace formatting for readability.
red
green
blue
purple
orange
yellow
brown
Listing 10.11 The desired output for the template that lists color names.
red
green
blue
purple
orange
yellow
brown
Listing 10.12 The output generated for the template in Listing 10.10.
不幸的是,目前这个问题还没有好的解决方案。目前的选择是完全放弃程序易读性,改良后的模板见Listing 10.13。利用笨重的VTL注释来滤出空白符(whitespace),见Listing 10.14。另外在这两者之间决定折衷方案。因为空白符(whitesapce)处理在Velocity里是一个经常讨论的主题,将来可能会有更好的解决方案。
#macro( write $list )
#foreach( $color in $list )
$color
#end
#end
red
green
#set( $favorite = "blue" )
$favorite
#if ( $likeViolet )
violet
#else
purple
#end
#write( ["orange","yellow","brown"] )
Listing 10.13 A modified version of the Listing 10.10 template that does away with attempts to improve readability.
#macro( write $list )
#**##foreach( $color in $list )
#* *#$color
#**##end
#end
##
red
green
##
#set( $favorite = "blue" )
$favorite
##
#if ( $likeViolet )
#**#violet
#else
#**#purple
#end
##
#write( ["orange","yellow","brown"] )
Listing 10.14 A modified version of the Listing 10.10 template that uses VTL comments to filter whitespace.
独立运行模式和非独立运行模式比较
Velocity提供了两种获得Velocity引擎实例的方式。遗赠(legacy)模式,迄今为止,我们所有的示例用的都是该模式,它是基于独立(Singleton)模式的。在这种模式下,在JVM里只有一个共享的Velocity引擎实例。很多情况下这种模式已经足够,而且它在资源共享方面有优势。然而,Velocity也支持非独立运行模式(non-singleton),有些时候使用这种模式要更适当一些。该模式允许在JVM里同时存在多个模板引擎的实例。在需要使用多个运行时配置的情况下,这种模式十分有用。这两种模式之间的唯一区别是所获得的Velocity引擎实例和初始化的区别,在独立模式下,Velocity引擎是通过暗中Velocity类的静态方法来获得的;在非独立模式下,是通过直接初始化Velocity对象来获得的。
本章小节和下章介绍
在这一章里,我们说明了如何发挥Velocity编程的最大功效。在下一章里,我们将探索Velocity和XML的交互,现时也将介绍一个很酷的工具Anakia,它可以让XML的管理更加轻松。