用Java程序生成文本的捷径

用Java程序生成文本的捷径
(仙人掌工作室 2001年09月04日 14:22)

大多数程序都需要输出一些文本,比如邮件消息、HTML文件或控制台输出。但是,计算机本质上只能处理二进制数据,程序员必须让软件来生成可理解的文本。在这篇文章中,我要介绍的是在生成和输出文本时,为何使用模板引擎能够节省时间。你将了解模板的优点,如何针对不同的情形创建高效的模板。和System.println说再见!

虽然程序员可以很轻松地编写出输出文字信息的代码(因为这毕竟是从Hello World范例学到的第一件事情),但通常而言,程序员不是写作或组织文字信息(如邮件)的最佳人选。因此,我们常常让市场部门或公关部门去做那些事情。但遗憾的是,即使对于最普通的邮件,编写者也常常依赖程序输出来完成任务。无论是对于邮件编写者还是程序员,这种合作方式都很容易带来误解和造成失误。

请看一个例子:一个Java程序从某个数据源收集一些客户信息,通过email给公司的每一个客户发送帐户余额信息。下面是完成这个任务的Java程序(完整的示例程序代码可以从本文最后下载):

  for (int i=0; i
{
Customer customer = (Customer)customers.get(i);

StringBuffer message = new StringBuffer();
message.append ("尊敬的先生/女士: ");
message.append (customer.getCustName());
message.append ("/n");
message.append ("/n");
message.append ("您的帐户余额是 ");
message.append (customer.getAccountTotal());
message.append ("/n");
message.append ("/n");
message.append ("致礼!");
message.append ("/n");
message.append ("某某装饰品公司");

// 发送email
mm.sendMail (customer.getFirstName(), customer.getEmail(),
"Account", message.toString());
}

上面的例子可谓发送消息最差劲的方法之一。由于消息嵌入到了程序代码之中,如果没有程序员的帮助,其他人几乎不可能对消息进行编辑。同时,即使对于专业的程序员,如果他不了解代码,要进行编辑也很困难。如果你预见了这些麻烦,把代码写成下面这种形式:

  static public final String STR_HELLO="尊敬的先生/女士: ";
static public final String STR_MESSAGE="您的帐户余额是 ";
static public final String STR_BEY="致礼!/n某某装饰品公司";

如果说上述代码使得消息编辑更容易,那么这种帮助也不会很多。很难要求一个不搞程序设计的人理解static和final的含义。此外,如果要改变消息的结构,上面这种代码也不够灵活。例如,人们可能要求你在邮件消息中加入更多来自数据源的信息,这时,你就得修改构造邮件的代码,或许还要添加更多的static final String对象。

模板简介
从文本文件装入消息文本可以解决部分问题——但不能提供动态内容,而这对于系统来说是很重要的。你需要有一种方法把动态内容插入到预先编写好的文本消息。但是,如果使用某种文本模板引擎,它就能够帮助你完成所有复杂的工作。

模板引擎解决了把动态内容插入文本消息的问题。使用模板引擎时,我们不再把消息直接嵌入程序,而是创建一个包含文本内容的简单文本文件,称为“文本模板”。模板引擎解析文本模板,借助一些简单的模板指令,把动态内容插入模板输出结果。

我选择的模板引擎是Jakarta Project的Velocity,但你可以任意选择其他许多模板引擎之一。VelocityWebMacro或许是当前功能最丰富、最受欢迎的两个引擎,而且两者都按照源代码开放协议免费提供。虽然我在本文例子中使用Velocity,你可以方便地把这些例子移植到不同的模板引擎,只需遵照目标引擎的语法即可。
我们来看看用Velocity完成的email程序例子。要编译和运行修改后的程序,你必须下载Velocity并把它加入到classpath。如果要让email部分也能正常运行,你还需要JavaMail
  for (int i=0; i<customers.size(); i++)
{
Customer customer = (Customer)customers.get(i);

// 创建一个环境,并加入所有的对象
VelocityContext context = new VelocityContext();
context.put ("CustName",customer.getCustName());
context.put ("total", new Double (customer.getAccountTotal()));
context.put ("customer", customer );

// 解析模板,生成结果字符串
StringWriter message = new StringWriter();
template.merge(context, message);

// 发送email
mm.sendMail (customer.getFirstName(), customer.getEmail(),
"Account", message.toString());
}

首先,你应该理解上面的Java源代码。这里我们不再象第一个例子那样生成文本,上面的代码引用一个称为“Velocity模板”的文本文件,然后把结果发送给收件人。Velocity模板可以是任何文本文件,但一般它包含一些用来插入动态内容的指令。

和VelocityContext相关的部分是上述代码中最值得注意的地方。VelocityContext提供了Java程序和Velocity文本模板之间的连接,而Velocity文本模板可以由其他人来编写。在模板中,所有加入到VelocityContext的对象都可以通过put()方法第一个参数指定的名字访问。为了解其工作过程,请看下面的模板文件:

尊敬的先生/女士: $CustName

您的帐户余额是 $total

致礼!
某某装饰品公司


Velocity引擎读取模板文件时,它直接输出文件中所有的文本,但以$字符开头的除外。$符号标识着一个位置,在模板的输出结果中,对象的值应该插入到$符号所指示的位置。例如,Java代码中有一个context.put ("CustName",customer.getCustName())语句,当Velocity模板引擎解析并输出模板的结果时,模板中所有出现$CustName的地方都将插入客户的名字;即,被加入到VelocityContext的对象的toString()方法返回值将替代Velocity变量(模板中以$开头的变量)。

模板引擎中最强大、使用最频繁的功能之一是它通过内建的映像(Reflection)引擎查找对象信息的能力。这个映像引擎允许用一种方便的Java“.”类似的操作符,提取任意加入到VelocityContext的对象的任何公用方法的值,或对象的任意数据成员。映像引擎还带来了另外一个改进:快速引用JavaBean的属性。使用JavaBean属性的时候,我们可以忽略get方法和括号(欲知详细信息,请参考模板引擎的说明文档)。请看下面这个模板的例子。由于在前面的Java代码示例中,我把Customer对象加入到了VelocityContext,所以我可以把模板改写成下面这种形式:

尊敬的先生/女士: $customer.CustName

您的帐户余额是 $customer.AccountTotal

致礼!
某某装饰品公司


除了替换变量之外,象Velocity和WebMacro这类高级引擎还能做其他许多事情。它们有用来比较和迭代的内建指令(尽管比较和迭代功能是两个模板引擎之间的共同点,但它们的语法差异很大,不能完全兼容。在选择模板引擎或者更换模板引擎时,务必注意这一点)。

举一个例子。十二月份,你的老板想要向所有的客户发一个圣诞节问候的email。你可以把这个消息加入到模板,以后再删除它。但这样的话,你得在新年那一天上班工作,以便删除圣诞问候消息。

一种更好的办法是指示模板何时显示圣诞问候消息。为此,你首先要把当前的月份加入到VelocityContext:

  int month = (new GregorianCalendar()).get(Calendar.MONTH);
// 把month值加1,因为它从0开始计算
context.put ("month", new Integer(month+1) );

现在,你只需在模板中进行比较:

尊敬的先生/女士: $customer.CustName

您的帐户余额是 $customer.AccountTotal

致礼!
某某装饰品公司

#if ($month == 12)
祝您和您的家人圣诞节快乐!
#end


#if指令的作用很清楚:对一个逻辑表达式进行测试,从而决定是否在模板输出结果中包含该指令块内的内容。除了简单的等于比较之外,你还可以执行更复杂的比较,但这方面的功能都与特定的模板引擎密切关联,所以这里我不再介绍。

迭代指令和#if指令一样简单。模板引擎支持迭代Java Collections Framework的任意实现,包括Array、List和Iterator。对于JDK 1.2或者更高版本,Java的Vector和ArrayList都实现了List接口,因此它们也适合在模板引擎的迭代指令中使用。

假设我们现在不想输出帐户余额,而是想通过迭代遍历帐户的交易记录,输出详细的报表。Customer对象的getTransactions()方法(参见下载包中完整的示例代码)返回一个List对象,List对象包含零个或者多个Transaction对象。由于List属于Java Collections Framework的一部分,我们可以用#foreach指令迭代其内容。我们不用担心如何定型对象的类型——映像引擎会为我们完成这个任务。从下面这个例子中,我们可以看出迭代的工作过程:

尊敬的先生/女士: $customer.CustName

#foreach ($transaction in $customer.Transactions)
$transaction.Description $transaction.Amount
#end

您的帐户余额是 $customer.AccountTotal

致礼!
某某装饰品公司


#foreach指令的一般格式是“#foreach <item> in <list>”。#foreach指令迭代list,把list中的每个元素放入item参数,然后解析#foreach块内的内容。对于list内的每个元素,#foreach块的内容都会重复解析一次。从效果上看,它相当于告诉模板引擎说:“把list中的每一个元素依次放入item变量,每次放入一个元素,输出一次#foreach块的内容”。

MVC设计模型
在看下一个例子之前,请回顾一下前面我们所讨论的内容。使用模板引擎最大的好处在于,它分离了代码(或程序逻辑)和表现(输出)。由于这种分离,你可以修改程序逻辑而不必担心邮件消息本身;类似地,你(或公关部门的职员)可以在不重新编译程序的情况下,重新编写邮件消息。

实际上,我们分离了系统的数据模式(Data Model,即提供数据的类)、控制器(Controller,即邮件程序)以及视图(View,即模板)。这种三层体系称为Model-View-Controller模型(MVC)。如果遵从MVC模型,代码分成三个截然不同的层,简化了软件开发过程中所有相关人员的工作(MVC的出现已经有一段时间,参见本文最后的“参考资源”了解更多信息)。

结合模板引擎使用的数据模式可以是任何Java对象,最好是使用Java Collection Framework的对象。控制器只要了解模板的环境(如VelocityContext),一般这种环境都很容易使用。一些关系数据库的“对象-关系”映射工具能够和模板引擎很好地协同,简化JDBC操作;对于EJB,情形也类似。

模板引擎与MVC中视图这一部分的关系更为密切。模板语言的功能很丰富、强大,足以处理所有必需的视图功能,同时它往往很简单,不熟悉编程的人也可以使用它。模板语言不仅使得设计者从过于复杂的编程环境中解脱出来,而且它保护了系统,避免了有意或无意带来危险的代码。例如,模板的编写者不可能编写出导致无限循环的代码,或侵占大量内存的代码。不要轻估这些安全机制的价值;大多数模板编写者不懂得编程,从长远来看,避免他们接触复杂的编程环境相当于节省了你自己的时间。

许多模板引擎的用户相信,在采用模板引擎的方案中,控制器部分和视图部分的明确分离,再加上模板引擎固有的安全机制,使得模板引擎足以成为其他内容发布系统(比如JSP)的替代方案。因此,Java模板引擎最常见的用途是替代JSP也就不足为奇了。

HTML处理
由于人们总是看重模板引擎用来替换JSP的作用,有时他们会忘记模板还有更广泛的用途。到目前为止,模板引擎最常见的用途是处理HTML Web内容。但我还用模板引擎生成过SQL、email、XML甚至Java源代码。在这里我只能涉及模板的部分应用,但你可以从本文最后的参考资源找到更多的例子。

我将在下面的HTML例子中使用前面email例子的数据模式。这个HTML页面是一个假想的企业Intranet页面,它显示出客户帐户的详细信息。本例中的控制器类是一个Java Servlet,视图部分则包含一个HTML模板。下面的代码显示了Servlet类中最主要的代码。为使这个例子更具有代表性,我从头开始手工编写这个Servlet。然而,一般情况下,模板会提供一些Servlet工具,帮助用户减轻一些编写代码的负担。

  // 装入模板
Template template = Velocity.getTemplate("html.vm");

// 创建环境
VelocityContext context = new VelocityContext();
context.put ("customers",Customer.getCustomers());

// 解析模板,输出应答
ServletOutputStream output = response.getOutputStream();
Writer writer = new OutputStreamWriter (output);
template.merge(context, writer);
writer.flush();


这个例子也没有什么令人惊异的地方。和前面的例子一样,我只是把必需的对象加入到VelocityContext,然后输出解析模板的结果。但是请注意,在前面的例子中,我只把一个Customer加入到VelocityContext,这里加入到VelocityContext的却是一组Customer对象。我可以用#foreach指令迭代访问所有的Customer对象。下面是相应的HTML模板:

<html>
<body>
<h1>客户报告</h1>
#foreach ($customer in $customers)
<h2>$customer.CustName<h2>

<table>
#foreach ($transaction in $customer.Transactions)
<tr>
<td width="200">
$transaction.Date
</td>
<td width="150">
$transaction.Description
</td>
<td width="100">
$transaction.Amount
</td>
</tr>
#end
</tr>
<td></td>
<td></td>
<td><b>$customer.AccountTotal</b></td>
<tr>
</table>

#end
</body>
</html>

如果你正在规划一个工程,这个工程的需求远远超过几个HTML模板,请考虑众多以模板为基础的框架之一。这些框架不仅为生成HTML提供了模板引擎所带来的便利,而且提供许多实用工具,比如数据库连接池和安全。两个常见的例子是TurbineMelati,它们都和Velocity以及WebMacro兼容,都是免费且源代码开放的产品。
性能和配置
对于大多数程序来说,模板的速度看来已经足够快;但对于大容量的Web网站,你可能要认真地考虑一下性能问题。在性能方面,模板引擎最大的特点在于模板缓冲。在模板缓冲机制的作用下,模板不再是每次出现请求的时候从磁盘读取,而是以最理想的方式在内存中保存和解析。在开发期间,模板缓冲通常处于禁用状态,因为这时请求数量较少,而且要求对页面的修改立即产生效果。部署完毕之后,模板一般不再改变,性能就成了优先考虑的问题。因此,这时你应该启用模板缓冲功能。

对于大多数模板引擎,你可以通过应用一个设置选项或编辑Java属性文件方便地启用模板缓冲功能。在Velocity中,你可以通过Properties对象初始化模板。至于Properties对象的创建方法,你既可以手工创建,就象我前面所做的那样;或者也可以从属性文件装入。在实际应用中,后者也许是较为理想的方法。

  Properties props = new Properties();
props.setProperty( "file.resource.loader.cache", "true" );
props.setProperty( "file.resource.loader.modificationCheckInterval", "3600" );
Velocity.init (props);

通过file.resource.loader.cache属性可以把缓冲设置成true或false,而file.resource.loader.modificationCheckInterval属性设置的是检查文件是否改变的间隔秒数。在这里我无法详细介绍所有的属性,请参考模板引擎的文档了解更多信息。

■ 结束语
免费的高级模板引擎使我们能够把模板功能加入到几乎所有的Java应用。这些模板引擎为程序员提供了易用的工具,为模板编写者提供了简单的模板语言,使得开发者更有信心编写出高质量的代码。

模板分离了程序代码和应用的表现部分,极大地方便了程序员和内容制作者的工作。模板把程序员从混合了大量文本信息的杂乱代码中解放出来;使得制作文本内容的人无需面对程序逻辑,就可以轻松地编写和修改内容。

模板清楚地分离了程序逻辑和文本表现代码,从而也为设计更好的MVC系统提供了方便。因此,模板为替换其他内容发布系统(比如JSP)提供了一种有吸引力的方案,因为它能够在不增加复杂性的情况下,改进应用的整体设计。

■ 参考资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值