Velocity开发者指南
(学习笔记)
一、Velocity 的工作原理
基本模式
当我们在Application或Servlet(实际上包括其他任何形式)中使用Velocity时,通常会做如下几件事:
·初始化Velocity。适用于Velocity的两种应用模式-单例(Singleton)和(separate runtime instance),并且你仅需要执行一次。
·创建一个Context对象。
·向Context对象添加数据。
·选择一个模板。
·合并模板和数据并输出。
通过org.apache.velocity.app.Velocity类,可以象这样在你的代码里应用单例模式:
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); |
这是最基本的使用方式,非常简单!但这恰恰就是当你用Velocity表现一个模版的时候所发生的事情!事实上,你并不一定要严格的按照这种方式写代码,因为我们为servlet和Application开发人员提供了一些更加简单的工具。
二、是否使用单例模式
在Velocity1.2和以后的版本中,开发人员有了两中使用Velocity引擎的方式:单例模式(singleton model)或分离实例模式(separate instance model)。两种方式采用相同的核心Velocity代码,这使得更加容易整和Velocity和Java应用程序。
单例模式
在JVM或者web应用中仅仅存在一个Velocity引擎的实例,并且所有应用都共享它。这个实例允许本地化配置和资源共享,可以通过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基类采用了单例模式,这是一个用于帮助开发servlets的实用类。尽管继承这个类是应用Velocity开发Servlets的最常用、最方便的方法,但是你仍然可以自由选择是否使用这个类。
分离实例
作为1.2版的一个新特性,你可以在同一个JVM或web应用中创建、配置和使用任意多的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");
|
正如你所看到的一样,非常的简单易懂!除了改变一些简单的语法,在你的应用中采用单例模式或分离实例模式不需要其他任何改变!
作为程序开发人员,你可以使用org.apache.velocity.app.Velocity和org.apache.velocity.app.VelocityEngine两个类同Velocity内部交互。但是,请记住,任何时候绝对不要在你的应用程序中使用内部的org.apache.velocity.runtime包中Runtime,RuntimeConstants,RuntimeSingleton或者RuntimeInstance类,因为它们仅仅是供内部使用的,并且以后可能会改变!
三、上下文
基础
上下文(context)是Velocity的核心概念,也是在系统各部分之间移动“数据容器”的一种常用技术。context作为Java层和模板层的数据载体!作为开发人员,你需要收集你的应用程序所需要的各种类型的对象,并把它们放在context中。作为设计者,可以通过 引用 来存取这些对象。通常,开发人员需要和设计人员一同研究决定应用中需要的数据。因此,这种协同开发值得多花费一些时间并仔细对需求进行分析!
Velocity允许开发人员创建自己的context类来支持特殊需求或技术(LDAP server),并提供了一个基础的实现类VelocityContext!VelocityContext适用于所有通常的需求,强烈建议使用它!仅仅在特殊和高级的案例中创建自己的context实现。
使用VelocityContext就象使用HashTable一样简单,这个接口包含了许多有用的方法,最常用的是:
public Object put(String key,Object value);
public Object get(String key);
需要注意的是,如同HashTable一样,值必须是Object类型的,并且不能为空!象 int,float等基本数据类型必须包装成适当的类类型。
这里是一些基本的context操作,更多信息请查看API。
通过#foreach()对迭代对象的支持
作为开发人员,你可以把多种对象存储到context中,但是也有一些限制,所以应该理解Velocity支持什么类型,以及可能会产生什么问题。Velocity可以在VTL的#foreach()方法中使用很多类型的集合。
·Object[]正常的对象数组。 Velocity会在内部使用一个提供Iterator接口的类来包装这个数组,但是开发人员和模板设计者不需要关系这个过程。
·java.util.Collection Velocity将调用iterator()方法获得一个Iterator,所以如果你的类实现了一个Collection接口,请确信iterator()方法会返回一个可用的Iterator.。
·java.util.Map 这里,Velocity将通过values()方法获得一个Collection接口,并通过这个接口调用iterator()方法获得Iterator。
·java.util.Iterator 注意:这只是暂时被支持的,原因是Iterator不支持reset。如果一个Iterator被存储在context中,并且被多个#foreach()方法调用,那么除第一个#foreach()方法外,其他的都将失败,因为Iterator不支持reset!
·java.util.Enumeration 同上
基于Iterator和Enumeration的限制,强烈建议仅仅在不可避免的时候才使用它们,并且如果可能的话,你应该让Velocity自己查找适当的可重用的迭代接口。
上下文链(Context Chaining)
Velocity的一个创新的特性就是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 ); |
在上面的例子中,我们把context2和context1连接起来。这意味着在模板中,你可以存取存放在VelocityContext中的任何对象,只要他们的key不重复就可以,如果key出现重复的话,那么context2中值将是可用的。但值得注意的是,实际上context1中重复的key并没
有被改变或破坏,仍然可以通过context1.get(“duplicate”)方法获得context1中deplicate的值!但请记住:在模板中没有任何方法获得context1中deplicate的值!同样,当你通过#set()方法向context中增加信息的时候,这个新的信息将被增加到最外层的context中的key中,所以,请不要试图将信息通过#set()方法增加到内层的context中!
在Template中创建对象
通常有两种情况需要在JAVA代码中处理由template在运行时创建的对象:
·template调用由JAVA代码放置到context中的对象的方法。
·JAVA代码在合并后存取由template放置到context中的对象。
关于context的一些其他问题
VelocityContext的一个特性是结点特殊的内省缓存(introspection caching)。通常,作为开发人员可以放心的把VelocityContext做为context使用。但是,必须知道关于这个特性的一个使用模式:
VelocityContext会堆积它访问过的模板中的语法结点的内省信息。所以在下述情况下:
·重复使用同一个VelocityContext迭代访问同一个模板
·模板缓存关闭
·在每个反复迭代中调用getTemplate()方法请求获得Template
VelocityContext可能会导致内存泄漏(实际上是聚集了太多的内省信息)。原因是VelocityContext 会堆积它所访问过的每个模板的内省信息,如果template缓存被关闭,将导致VelocityContext每次都访问一个新的模板,从而堆积更多的内省信息。
强烈建议你做如下的事情:
·当template处理结束后创建一个新的VelocityContext,这将阻止堆积内省信息。如果你需要重用携带数据和对象的VelocityContext,可以简单的用另一个VelocityContext来包装它。外层的VelocityContext将会堆积内省的信息,但另人兴奋的是你将丢弃它!
·开启模板的缓存机制!这将阻止每次都对template重新解析,这样VelocityContext不仅可以避免增加内省信息,同时还可以改进程序。
·在循环迭代期间重用Template对象。这样,当缓存关闭的时候就不用强迫Velocity一次又一次的去重新读取和重新解析同样的template,因此,VelocityContext也就不会每次都堆积新的内省信息!
四、在Servlets中使用Velocity
Servlet编程
Velocity的一个主要的应用领域就是JAVA Servlet。有很多理由可以说明Velocity适合这个领域,最关键的是Velocity强制视图层(VIEW)和代码分离!
在servlet中使用velocity非常的简单。基本上你要做的就是继承VelocityServlet基类和实现handRequest()方法。
从Velocity1.1开始,有两个handRequest()方法:
public Template handRequest(Context)
这是旧的方法。这个方法要求返回一个有效的Template对象。如果无效或者为空,将会产生异常!同时error()方法会被调用!如果你希望产生异常后做一些其他的事情(比如重定向)可以重写这个方法。强烈建议您使用新的方法!
public Template handRequest(HttpServletRequest,HttpServletResponse,Context)
这是新方法。与旧的方法不同,它可以返回一个null来说明方法已经执行,并且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;
}
}
|
是不是觉得很面熟?除了创建context对象,这已经由VelocityServelt帮你做了,并且VelocityServlet也帮你实现了merge()方法,这和我们开始部分的编码方式基本上是一样的。我们获得context和应用程序数据,并反回一个template。
默认传递给handleRequest()方法的context对象包含当前的HttpServletRequest和HttpServletResponse对象。他们分别被放置在常量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允许开发者重写它的一些方法,以更好的使用它。
Properties loadConfiguration(ServletConfig)
这个方法允许重写通常的配置机制,增加或修改配置属性。
Context createContext(HttpServletRequest,HttpServletResponse)
这个方法允许你创建自己的Context对象。默认的实现只是简单的包含request和response的VelocityContext对象。为了避免在一些servlet容器中发生内省缓存问题,这个resquest和response对象被一个简单的包裹器类包装了。你可以正常的使用他们,但是请注意他们并不是javax.servlet.XXX !
void mergeTemplage(Template,Context,HttpServletResponse)
这个方法允许你产生输出流。VelocityServlet提供了一个有效的Writer类的池,所以只有在特殊的情况下才重写这个方法。
protected void error(HttpServletResquest,HttpServletResponse,Exception)
用于处理在处理请求过程中产生的异常。默认的实现将发送一个包含错误信息的简单HTML给用户。可以重写这个方法实现更高级的处理。
部署
当你部署基于Velocity的Servlet时,请确保你的属性文件被用来配置Velocity运行时刻环境。在Tomcat中,一个不错的方法就是放置你的velocity.properties文件在你的web应用的根目录下,并在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应用的classloader会管理你的运行时刻实例,把velocity-xx.jar包放在WEB-INF/lib目录下是一个好主意!不要放在CLASSPATH或容器的根路径下。
(待续)