Velocity空间

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

CHATPER 12 在Servlets里使用 Velocity

 

通过前面章节的学习,我们了解到Velocity主要用作MVC框架View组件的脚本语言。ServletsJavaMVC框架里用得最多的编程方式。通过把Servlets用作控制器(controller),Velocity用作显示层(View),把ServletsJavaBeans作用模型(model),这样就创建了一个理想的Web开发全面解决方案。在这一章里,我们将学习如何在Servlets里使用Velocity

使用Servlets

在开始学习这章内容之前,你必须充分理解Servlets是什么?以及它是如何工作的?如果对此你已经很熟悉了,那么你可以直接跳到VelocityServlet扩展Servlets”节进行深入学习。

在动态Web页面开发的早期,服务端语言,比如ASPJSP等主要用于在HTML页面里嵌入服务器端执行的代码。当浏览器请求这个页面的时候,服务器立即对所请求的ASP/JSP页面进行解析,执行里面的语句,并向用户返回一个纯粹的HTML页面,有时还要附带返回客户端执行的JavaScriptVBScript脚本。混合HTML和服务器端代码并不容易,而且,服务器端脚本语言还要受到一些限制,比如服务器平台支持、服务器支持等比较有限。

Servlets把代码从HTML中提取出来,把它放到服务器上,这样,你就可以使用全功能的Java语言进行Servlets逻辑开发。Servlets也能进行分布式处理,而且可以使用模板来向用户提供信息显示。

Servlets的通用格式

在进行深入学习之前了解传统的Servlets结构是非常有必要的。Listing 12.1显示了一个简单的Servlets

import java.io.*;

import java.sql.*;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.naming.*;

public class ViewAccount extends HttpServlet {

public void init() throws ServletException {

}

public void doGet(HttpServletRequest request,

HttpServletResponse response)

throws IOException, ServletException {

response.setContentType("text/html");

PrintWriter out = response.getWriter();

out.println("<HTML>");

out.println("</HTML>");

} catch (SQLException e) {

e.printStackTrace();

}

}

public void doPost(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

doGet(request, response);

}

}

Listing 12.1 传统servlet代码

当有一个web浏览器请求达到时,一般情况下,请求的方式为POSTGET。在上面的代码中你可以看到,其中有一些方法将用于处理这两种请求,分别为doPost()doGet()方法,其中doPost()方法只是简单委派给了doGet()方法。在doGet()方法内部有两个重要的变量:requestresponserequest对象用于把用户信息转换成servlet能够理解的信息。特别的,用户在HTML窗体里的输入将作为文本进行处理。response对象主要负责向用户的浏览器返回标准的HTML页面。response对象返回的信息包括HTMLXML和图片。response对象也可以和PrintWriter对象一起协同工作,以用于写入信息并返回给用户。

Servlets不能使用普通的Web服务器作为主机,只能使用应用程序服务器(application servers)作为主机。可以作为Servlets主机的软件有TomcatJBOSSResinWEBlogicapusic等。在很多情况下,应用程序服务器被用于和Web服务器一起协同工作,比如IISApache等。当Web服务器得到一个servlet请求时,这个请求被移交给应用程序服务器执行。应用程序服务器使用java编译器来创建一个可执行的Servlets映像,这个Servlets映像将在JVM里执行。我们将在下一章演示这个话题的示例。

VelocityServlet扩展Servlets

在某种程度上,你可以使用Velocity引擎和servlet,通过Velocity模板语言书写代码来为用户创建信息输出。为了让Servlets的使用更容易,我们将通过使用一个名叫VelocityServlet的基类和名叫handleRequest()的方法来代替传统ServletsHttpServletdoGet()/doPost()方法。

handleRequest()方法传递三个参数HttpServletRequestHttpServletResponseContext来响应GET/POST请求。HttpServletRequestHttpServletResponse对象和doGet()doPost()方法接受的是同一个对象(代码见Listing 12.1)。上下文对象是一个用于在Servlets里使用的Velocity引擎的上下文。我们将在上下文对象里为用于向用户显示响应的Velocity模板放置信息。

handleRequest()方法返回一个模板对象,该方法自动把传递进来的上下文对象进行合并。因而,在handleRequest()方法里的代码,主要完成设置上下文和合并返回的Velocity对象等操作。如果返回的模板对象为null,这些代码就不能完成合并操作。在这种情况下,和传统的Servlets代码一样,这些代码将向用户浏览器返回一些放置在response对象里的PrintWriter对象里的东西。

基本的Velocity Servlet代码

handleRequest()方法示例代码见Listing 12.2,用于向浏览器简单输出字符串。

import java.util.Vector;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.velocity.Template;

import org.apache.velocity.context.Context;

import org.apache.velocity.servlet.VelocityServlet;

import org.apache.velocity.exception.*;

public class VelocityServletExample extends VelocityServlet {

public Template handleRequest( HttpServletRequest request,

HttpServletResponse response,

Context context ) {

Vector v = new Vector();

v.add("one");

v.add("two");

v.add("three");

context.put("list", v);

Template template = null;

try {

template = getTemplate("displaylist.vm");

} catch( Exception e ) {

PrintWriter out = response.getWriter();

out.println("Error getting template");

}

return template;

}

}

Listing 12.2 A Velocity servlet example.

当浏览器调用Listing 12.2里的代码时,初始化一个Velocity对象,并在里面放置了三个字符串对象。Velocity对象通过下面的语句被放入上下文中。

context.put("list", v);

注意,我们在上下文里放置的是Vector对象实体(java.util.Vector)。接着,声明了一个模板对象,并使用getTemplate(String)方法来从服务器的磁盘上定位和加载模板。如果在获取模板文件的过程中出现错误时,response对象里的PrintWriter被获得,并向其中写入错误信息。最后一个操作是返回模板对象。如果某些错误导致这个对象为null,系统将不能完成合并操作;否则,从服务器读入的模板将和上下文一起被合并。Listing 12.3为这个示例所使用的Velocity模板。

<?xml version="1.0" encoding="ISO-8859-1" ?>

<list>

#foreach( $value in $list )

<number>$value</number>

#end

</list>

Listing 12.3 The displaylist.vm Velocity code.

Listing 12.3里的模板不是HTML格式的,我们使用XML来演示Velocity模板并不仅限于使用HTML格式。了解这个示例是如何执行的非常重要。还记得“list”关键字和Vector对象关联,在模板里的Velocity模板语言代码通过#foreach循环指令使用Vector。回想一下在List对象(如Vector)里使用#foreach指令的定义我们就可以发现,迭代器(iterator)自动提取并用于获取所有在List(在这种情况下还可以是Vector)里的独立对象。

所有来自Vector的每一个对象都被提出并放置到引用$value处。既然字符串不是混合对象,我们就可以简单地输出它的值(在<number>元素标记里)。Figure 12.1演示了执行结果。


Figure 12.1
Output from the Velocity template.

HttpServletRequestHttpServletResponse对象被传递给Servlets代码,同时被放置在两个上下文常量对象中:

VelocityServlet.REQUEST—Stored as req

VelocityServlet.RESPONSE—Stored as res

 

每一个对象都能通过直接调用引用名(reqres)的方式用于Velocity模板中。

#set($username = $req.getParameter('username'))

创建一个MVC应用程序

我们的第一个Velocity示例相当简单,现在我们来搞一个稍复杂一点的示例。在这一节里,你将使用Servlets来构建一个MVC应用程序,其中:Servlets用作控制器(controller)Velocity模板作用视图(view)JavaBeans作用模型(model)。这个应用程序是一个具有以下四个功能的CD数据库:

增加一个CD,同时返回一个thank-you响应

为特定的CD增加歌曲track,同时返回一个thank-you响应

通过特定的艺术家返回其所有的CD

获取特定CD的全部歌曲track

Figure 12.2显示了增加一个新CD时的情况;Figure 12.3显示了当你使用特定的艺术家搜索CD时的情况;Figure 12.4显示了一个特定CD的所有歌曲时的情况。


Figure 12.2
Adding a new CD.


Figure 12.3
Listing all CDs for an artist.

数据库结构

这个应用程序使用了一个数据库来保存CD和所有歌曲的信息。在这个测试环境,我们选择MYSQL数据库来存在数据,当然,你也可以选择其他类型的数据库。下面的SQL建表命令显示了CD和歌曲表的数据结构。

create table cd (

id int not null primary key auto_increment,

title varchar(128),

artist varchar(64),

tracks int);

create table tracks(

id int not null primary key auto_increment,

cd_id int,

name varchar(64),

length varchar(16));


Figure 12.4
Listing all tracks on a CD.

数据库访问

我们的模型组件打算使用实体EJBs来访问CD表和歌曲(tracks)表的数据,这些beanServlets被用于向用户提供服务。在这个测试环境里,我们使用了Resin应用程序服务器。EJBs通过JNDI(Java名字和目录接口)资源引用来访问数据库。Listing 12.4显示了在应用程序服务器的配置文件中增加的 <resource-ref>元素。除了数据库驱动和URL外,这个元素使用的是标准规范。如果你使用的是另外数据库,你需要改变<init-param>元素以满足需要。

<resource-ref>

<res-ref-name>jdbc/ProductsDB</res-ref-name>

<res-type>javax.sql.ConnectionPoolDataSource</res-type>

<init-param driver-name="org.gjt.mm.mysql.Driver"/>

<init-param url="jdbc:mysql://localhost:3306/products"/>

<init-param user=""/>

<init-param password=""/>

<init-param max-connections="20"/>

<init-param max-idle-time="30"/>

<init-param max-active-time="1"/>

<init-param max-pool-time="1"/>

<init-param connection-wait-time="1"/>

</resource-ref>

Listing 12.4 The resin.config resource text.

模型(Model)代码

在这里,你已经创建了一个数据库,同时包含了JNDI引用,现在,通过JNDI引用,你就可以访问数据库了。是时候考虑实体beans了,我们将把它用于访问数据库表里的数据。因为在我们的有两个数据库表,因此就需要构建两个EJB。为了达到目的,让我们利用Resincontainer-managed persistence (CMP)模式来创建bean,同时主要把精力集中在实现EJB本地实例上。意思是我们只处理CMP的本地接口,而不处理远程接口,因此只需要很少的代码。由于本书是关于Velocity的,故我们只展示这两个bean的代码,不作更多解释,你可以在站点http://www.wiley.com/compbooks/gradecki处下载这个完整的示例代码。下面就让我们来看一看EJB文件的定义(Listing 12.5)

<ejb-jar>

<enterprise-beans>

<entity>

<ejb-name>CDRecordBean</ejb-name>

<local-home>cd.CDRecordHome</local-home>

<local>cd.CDRecord</local>

<ejb-class>cd.CDRecordBean</ejb-class>

<prim-key-class>int</prim-key-class>

<primkey-field>id</primkey-field>

<persistence-type>Container</persistence-type>

<reentrant>True</reentrant>

<abstract-schema-name>CDTable</abstract-schema-name>

<sql-table>cd</sql-table>

<cmp-field><field-name>id</field-name></cmp-field>

<cmp-field><field-name>title</field-name></cmp-field>

<cmp-field><field-name>artist</field-name></cmp-field>

<cmp-field><field-name>tracks</field-name></cmp-field>

<query>

<query-method>

<method-name>findByArtist</method-name>

</query-method>

<ejb-ql>SELECT o FROM CDTable o WHERE o.artist like

?1</ejb-ql>

</query>

</entity>

<entity>

<ejb-name>TracksRecordBean</ejb-name>

<local-home>cd.TracksRecordHome</local-home>

<local>cd.TracksRecord</local>

<ejb-class>cd.TracksRecordBean</ejb-class>

<prim-key-class>int</prim-key-class>

<primkey-field>id</primkey-field>

<persistence-type>Container</persistence-type>

<reentrant>True</reentrant>

<abstract-schema-name>TrackTable</abstract-schema-name>

<sql-table>tracks</sql-table>

<cmp-field><field-name>id</field-name></cmp-field>

<cmp-field><field-name>cd_id</field-name></cmp-field>

<cmp-field><field-name>name</field-name></cmp-field>

<cmp-field><field-name>length</field-name></cmp-field>

<query>

<query-method>

<method-name>findByCdID</method-name>

</query-method>

<ejb-ql>SELECT o FROM TrackTable o WHERE o.cd_id=?1</ejb-ql>

</query>

</entity>

</enterprise-beans>

</ejb-jar>

Listing 12.5 The CDRecordBean EJB file.

Listing 12.5里的EJB文件展示了这两个bean是如何定义的,其中包含了主键和每一个表的字段。这两个实体bean都包含了一个<query>元素,它允许数据从表中取出并用于除主键以外的字段。为了让你了解在Resin应用程序服务器里是如何书写实体bean的,让我们来看一下Listing 12.6里的CDRecordBean类。

package cd;

import javax.ejb.*;

public abstract class CDRecordBean

extends com.caucho.ejb.AbstractEntityBean {

public abstract String getTitle();

public abstract String getArtist();

public abstract int getTracks();

public abstract int getId();

public abstract void setTitle(String title);

public abstract void setArtist(String artist);

public abstract void setTracks(int tracks);

public abstract void setId(int id);

public int ejbCreate(String title, String artist, int tracks)

throws CreateException {

setId(0);

setTitle(title);

setArtist(artist);

setTracks(tracks);

return 1;

}

public void ejbPostCreate(String title, String artist, int

tracks) {

// since there are no relations, this is empty.

}

}

Listing 12.6 The CDRecordBean class.

Listing 12.6里的CDRecordBean类继承自AbstractEntityBean类,AbstractEntityBean类是Resin制造商定义的一个助手类。这个助手类提供了所有的用于让实体bean实现空body的通用方法。事实上,把该助手类用于你的bean时,你只需要提供一个方法,从而减少代码量和代码混杂。另外,bean里剩余的代码被用于定义与这个bean相关联的表字段。Listing 12.7显示了TracksRecordBean类。

package cd;

import javax.ejb.*;

import java.sql.*;

public abstract class TracksRecordBean

extends com.caucho.ejb.AbstractEntityBean {

public abstract int getId();

public abstract int getCd_id();

public abstract String getName();

public abstract String getLength();

public abstract void setId(int id);

public abstract void setCd_id(int cd_id);

public abstract void setName(String name);

public abstract void setLength(String tracks);

public int ejbCreate(int cd_id, String name, String length)

throws CreateException {

setCd_id(cd_id);

setName(name);

setLength(length);

return 1;

}

public void ejbPostCreate(int cd_id, String name, String length) {

// since there are no relations, this is empty.

}

}

Listing 12.7 The TracksRecordBean class.

我们在之前曾经提及,你可以为这些实体bean下载剩余的文件。所有的实体文件被放置在Resin 应用程序服务器主机的/classes目录下。

The View Code

现在,你需要考虑一下如何使用Velocity作为你的脚本语言来进行输出。这里将是你施展魔法的地方,为用户提供令人激动的输出。对这个应用程序来说,我们需要三个Velocity模板:

thanks.vm—显示thank-you信息的普通页面

displaycd.vm—向用户显示CD列表的页面

displaytracks.vm—显示特定CD内所有歌曲的页面

首先,让我们来看一下thanks.vm模板,见Listing 12.8

<HTML>

<HEAD>

<TITLE></TITLE>

<link rel="stylesheet" type="text/css" href="defaultpage.css">

</HEAD>

<BODY BGCOLOR="#F79C19" link="ffffff" alink="999999"

vlink="ffffff" topmargin="0" leftmargin="0" marginheight="0"

marginwidth="0">

<BR>

#if ($message)

$message

#end

$thanks

<img src="/images/tune_big.gif" height="250" width=283></td></tr>

</table>

</BODY>

</HTML>

Listing 12.8 The thanks.vm template.

这个thanks.vm模板事实上是web框架集的一部分,它提供了所有可见的边框。这个模板将被放置在main处,或放在body里,是web框架集的一部分。在这里,为了让页面更美观,你需要提供适当的背景颜色和图片。在这个模板的顶部是页面背景的信息,后跟实际的Velocity元素。

第一个元素为带有$message引用的#if指令。$message引用用于显示一个信息(当一个错误出现并且你需要让用户知道这个错误的时候)。这个信息只有在错误发生时才会被写入上下文中,因此,程序没有发生错误的时候,这个引用在上下文里是不存在的。如果我们不用#if指令对$message进行测试,那么在输出中就有可能出现文本字符串“$message”,这看起来不太好,因此我们使用$if指令对$message引用是否包含有值进行测试,如果$message引用包含有值(意思是在上下文里可以找到这个引用),那么就显示$message引用的值。

在发生错误的情况下,你仍旧想感谢用户辛苦输入新的CD或歌曲。这个输出就$thanks引用来产生。

 

显示CD

我们的应用程序允许用户通过特定的艺术家搜索数据库里所有的CD,并得到一个这些CD的列表。在每一次显示CD列表的时候都将显示一个按钮,以方便用户可以单击来列出特定CD的歌曲列表。Listing 12.9里的displaycd.vm模板用于处理这些显示任务。

<HTML>

<HEAD>

<TITLE></TITLE>

<link rel="stylesheet" type="text/css" href="defaultpage.css">

</HEAD>

<BODY BGCOLOR="#F79C19" link="ffffff" alink="999999"

vlink="ffffff" topmargin="0" leftmargin="0" marginheight="0"

marginwidth="0">

<BR><BR>

<font color="ffffff">

#foreach($value in $cds)

<form action="http://localhost:8080/cd/cdVelocityHandler"

method="post">

<b> Title: $value.title </b>

<input type="hidden" name="id" value="$value.id">

<input type="submit" name="submit" value="tracks">

</form>

#end

<BR>

<img src="/images/tune_big.gif" height="250" width="150">

</BODY>

</HTML>

Listing 12.9 The displaycd.vm template.

thanks.vm模板一样,在这个模板的开头部分包含了一些HTML标记。所有CD的输出将被显示在框架集的body内。伴随HTML而来的是Velocity代码。现在让我们回忆一下需要列表显示的两个部分:显示某艺术家的所有CD标题和显示一个用于列出每个CD的所有歌曲的按钮。

假如你正和你的web开发者一起工作,你决定让代码执行一个操作,把从数据库里提出来的CD放到一个名叫$cdsCollection对象里,并将这个$cds对象放入上下文中。这个Collection对象将包含许多基于某特定艺术家所有CDCDRecordBean对象。

正如你已经看到的,#foreach指令用于从可用的对象里提取一个迭代器(iterator),Collection类就是这样的一个对象。因此,每一次循环,$value引用将得到一个CDRecordBean实体对象。利用beangetter方法,适当的值将被提取出来。

许多工作都发生在循环内部,在这里将从数据库提取用于显示的CD标题。注意,一定要用HTML<form>标记包围这个标题,这个<form>标记用于显示一个让用户单击就可以显示所有这张CD歌曲的按钮。

在讨论完应用程序的需求后,你和web开发者决定从CD表里提取ID,用于链接歌曲表。为了实现这个想法,你在<form>元素里创建了一个隐藏的<input>元素。注意,这个隐藏输入按钮的值用的是$value.id,意思是你可以向控制器传递每一个CDID

显示歌曲(tracks)

当用户单击displaycd.vm模板输出的Tracks按钮时,将显示歌曲的名称和歌曲长度。Listing 12.10显示的displaytracks.vm模板将用于完成这些任务。这个模板再次包含了HTML标记,以便更美观地显示这个模板。在HTML标记之后,你将看到另一个#foreach循环。

<HTML>

<HEAD>

<TITLE></TITLE>

<link rel="stylesheet" type="text/css" href="defaultpage.css">

</HEAD>

<BODY BGCOLOR="#F79C19" link="ffffff" alink="999999"

vlink="ffffff" topmargin="0" leftmargin="0" marginheight="0"

marginwidth="0">

#if ($message)

$message

#end

<BR>

#foreach($value in $tracks)

<b> Track: $value.name - Length: $value.length</b><BR>

#end

<br>

<img src="/images/tune_big.gif" height="250" width=283></td></tr>

</table>

</BODY>

</HTML>

Listing 12.10 The displaytracks.vm template.

在这里,你的web开发者已经指出了你所需要的另外一个Collection对象,用于存储某张CD上所有的歌曲。这个Collection对象被命名为$tracks,它通过控制器组件放置在上下文里。

控制器代码

你目前已经拥有了模型和视图组件,现在你需要一个控制器组件来把这两个组件联系在一起。Listing 12.11显示了这个Velocity Servlets,它将完成这个工作。

import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

import org.apache.velocity.Template;

import org.apache.velocity.context.Context;

import org.apache.velocity.servlet.VelocityServlet;

import org.apache.velocity.exception.*;

import javax.naming.*;

import javax.ejb.*;

import cd.*;

import org.apache.velocity.app.Velocity;

public class cdVelocityHandler extends VelocityServlet {

private CDRecordHome cdHome = null;

private TracksRecordHome tracksHome = null;

protected Properties loadConfiguration(ServletConfig config )

throws IOException, FileNotFoundException {

Properties p = new Properties();

String path =

config.getServletContext().getRealPath("/");

if (path == null) {

System.out.println("

Unable to get the current webapp root");

path = "/";

}

p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH,

path );

return p;

}

public void init() throws ServletException {

try {

javax.naming.Context cmp = (javax.naming.Context)

new InitialContext().lookup("java:comp/env/cmp");

cdHome = (CDRecordHome) cmp.lookup("CDRecordBean");

tracksHome = (TracksRecordHome)

cmp.lookup("TracksRecordBean");

} catch (NamingException e) {

e.printStackTrace();

}

}

public Template handleRequest( HttpServletRequest req,

HttpServletResponse res, Context context ) {

Template template = null;

if (req.getParameter("submit").equals("new")) {

try {

if (cdHome == null) {

context.put("message", "Sorry we had an error");

} else {

int tracks =

Integer.parseInt(req.getParameter("tracks"));

CDRecord cd =

cdHome.create(req.getParameter("title"),

req.getParameter("artist"), tracks);

if (cd != null) {

context.put("thanks",

"Thank you for the new CD<BR>");

} else {

context.put("thanks",

"We are sorry but your request failed<BR>");

}

try {

template = getTemplate("thanks.vm");

} catch( Exception e ) {

e.printStackTrace();

}

}

} catch(Exception e) {

e.printStackTrace();

}

} else if (req.getParameter("submit").equals("obtain")) {

try {

if (cdHome == null) {

context.put("message", "Sorry we had an error");

} else {

Collection cds =

cdHome.findByArtist(req.getParameter("artist"));

context.put ("cds", cds);

try {

template = getTemplate("displaycds.vm");

} catch( Exception e ) {

e.printStackTrace();

}

}

} catch(Exception e) {

e.printStackTrace();

}

} else if (req.getParameter("submit").equals("tracks")) {

try {

if (tracksHome == null) {

context.put("message", "Sorry we had an error");

} else {

int id = Integer.parseInt(req.getParameter("id"));

Collection tracks = tracksHome.findByCdID(id);

context.put ("tracks", tracks);

try {

template = getTemplate("displaytracks.vm");

} catch( Exception e ) {

System.out.println("Error " + e);

}

}

} catch(Exception e) {

e.printStackTrace();

}

} else if (req.getParameter("submit").equals("addtrack")) {

try {

if (tracksHome == null) {

context.put("message", "Sorry we had an error");

} else {

int id= Integer.parseInt(req.getParameter("id"));

TracksRecord track = tracksHome.create(id,

req.getParameter("name"),

req.getParameter("length"));

if (track!= null) {

context.put("thanks",

"Thank you for the new track<BR>");

} else {

context.put("thanks",

"We are sorry but your request failed<BR>");

}

try {

template = getTemplate("thanks.vm");

} catch( Exception e ) {

e.printStackTrace();

}

}

} catch(Exception e) {

e.printStackTrace();

}

} else {

}

return template;

}

}

Listing 12.11 The controller servlet for the CD example

这个控制器组件是一个Velocity Servlets,它使用VelocityServlet类的handleRequest()方法。在这里有一些让Servlets访问Velocity模板和实体EJB的步骤。

Velocity模板被放置在Servlets应用程序的根目录,这个目录是Servlets必须的。为了确定Servlets能够访问这个应用程序的根目录,需要为FILE_RESOURCE_LOADER_PATH属性设置真实的目录。你可以在loadConfiguration()方法里完成这个设置操作,这个方法将在handleRequest()方法调用之前被自动调用。

接着,你需要获得实体EJB home接口的访问权,你可以在init()方法里完成这个任务,init()方法将Servlets第一次执行时被调用,这个方法将为comp/env/cmp JNDI引用获得一个naming.Context上下文对象。这个引用将用于在系统里访问EJB。接下来,你需要查找每一个bean,并从这两个bean中返回适当的home对象。这些home对象将用于构建实体bean

handleRequest()方法里的代码用于检查用户请求的是这四个操作的哪一个操作(增加CD、搜索特定艺术家、查看某CD的全部歌曲、为CD增加歌曲)。让我分别看一下这四个操作:

增加新CD

当用户为一张新的CD输入标题、艺术家和该CD的所有歌曲时,代码必须能够完全把这些数据放到数据库表里。代码首先确定home接口对象是有效的,如果该接口无效,一个错误信息将会指派给“message”引用,该引用将增加到上下文中。

如果home接口对象是有效的,则从HTML<form>传递过来的歌曲字符串被转换成integer。接着,歌曲和从<form>传递过来的值的剩余部分一同被传递给CD beanhome接口的create()方法。调用该方法的结果有可能是null,也可能是一个用于呈现CD表行的新的实体EJB。如果该方法的值不是null,那么“thanks”引用被指向一个文本字符串,同时被加入到上下文对象中。否则,一个失败的信息将会加入到上下文中。

在任意一种情况下,模板对象都会被设置成从getTemplate()方法(使用thanks.vm文件名参数)返回的值。如果没有异常发生,这个新模板对象会从handleRequest()方法返回,同时用户会看到适当的输出。

增加新CD歌曲(Track

增加新CD歌曲的代码基本上和增加新CD的代码一样,只是接口不同,在此用的是track接口。在一个生产型(production)系统里,你必须确定歌曲应该加入到的CD表,以保证CD和歌曲能够正确关联。

通过艺术家搜索CD

通过特定的艺术家来获取CD列表和增加CD只有一小点的不同就是获取CD列表需要查询数据库。你应该记得这两个实体EJBEJB文件里都定义了一个<query>元素。在CD数据表的情况下,我们定义的查询将返回特定艺术家所有的CD表行。

代码通过findByArtist(String)方法来调用这个查询。从这个方法返回的值是一个Collection对象,它包含了从CD数据表返回的零到若干个实体对象。不管有多少个对象在collection里,我们将使用下面的命令把这个Collection加入到上下文中

context.put ("cds", cds);

Collection对象被加入到上下文中后,displaycds.vm模板就从服务器的磁盘取出,之后,这个模板对象被显示给用户。

列出CD歌曲

当用户想显示特定CD的歌曲时,代码将获取从<form>里的ID变量传递过来的ID,以及将这个ID传递给findByCdID(int)方法。这个方法执行TracksRecordBeanEJB文件里的<query>元素,该方法的结果是一个Collection对象,它包含了这张特定CD的所有歌曲。这个Collection对象通过$tracks引用被加入到上下文中。

高级Servlet功能

作为VelocityServlet基础类,许多附加的方法可以被重载。这些方法如下:

Properties loadConfiguration(ServletConfig)—一个允许附加的属性被加入到Servlets的属性中的方法。这些目前被定义到Velocity运行时的属性是:

l         static java.lang.String--COUNTER_INITIAL_VALUE—初始化#foreach指令的计数器值

l         static java.lang.String--COUNTER_NAME—初始化#foreach指令的计数器名称

l         static java.lang.String--DEBUG_PREFIX—日志信息前缀

l         static java.lang.String--DEFAULT_RUNTIME_DIRECTIVES—默认运行时指令

l         static java.lang.String--DEFAULT_RUNTIME_PROPERTIES—默认运行时属性

l         static java.lang.String--ENCODING_DEFAULT—默认编码类型

l         static java.lang.String--ERROR_PREFIX—错误信息前缀

l         static java.lang.String--ERRORMSG_END—错误信息的结束标记,通过在#include指令里传递一个不允许参数时触发

l         static java.lang.String--ERRORMSG_START—错误信息的开始标记,通过在#include指令里传递一个不允许参数时触发

l         static java.lang.String--FILE_RESOURCE_LOADER_CACHE—FileResourceLoader里打开一个公用的缓存

l         static java.lang.String--FILE_RESOURCE_LOADER_PATH—FileResourceLoader里设置一个公用的路径

l         static java.lang.String--INFO_PREFIX—消息通知前缀

l         static java.lang.String--INPUT_ENCODING—模板编码集

l         static java.lang.String--INTERPOLATE_STRINGLITERALS—字符串篡改开关

l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_BUFFER_SIZE--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_FROM--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_SERVER--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_SUBJECT--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_TO--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_FILE_BACKUPS--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_FILE_SIZE--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_PATTERN--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_REMOTE_HOST--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_REMOTE_PORT--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_SYSLOGD_FACILITY--log4J配置

l         static java.lang.String--LOGSYSTEM_LOG4J_SYSLOGD_HOST--log4J配置

l         static int--NUMBER_OF_PARSERS—你想创建的解析器数量

l         static java.lang.String--OUTPUT_ENCODING—输出流编码集

l         static java.lang.String--PARSE_DIRECTIVE_MAXDEPTH—#parse指令允许的最大递归深度

l         static java.lang.String--PARSER_POOL_SIZE—解析器在池里的总数

l         static java.lang.String--RESOURCE_LOADER—用于重新找回资源加载器名称的关键字

l         static java.lang.String--RESOURCE_MANAGER_CACHE_CLASS—实现资源管理缓存的类

l         static java.lang.String--RESOURCE_MANAGER_CLASS—实现资源管理器的类

l         static java.lang.String--RESOURCE_MANAGER_LOGWHENFOUND—用于确定是否对找到资源进行日志

l         static java.lang.String--RUNTIME_LOG—定位Velocity日志文件

l         static java.lang.String--RUNTIME_LOG_ERROR_STACKTRACE—堆栈跟踪错误信息输出

l         static java.lang.String--RUNTIME_LOG_INFO_STACKTRACE—堆栈跟踪报告信息输出

l         static java.lang.String--RUNTIME_LOG_LOGSYSTEM—用于指定外面的日志系统

l         static java.lang.String--RUNTIME_LOG_LOGSYSTEM_CLASS—你想要使用的日志系统类

l         static java.lang.String--RUNTIME_LOG_REFERENCE_LOG_INVALID—非法引用日志

l         static java.lang.String--RUNTIME_LOG_WARN_STACKTRACE—堆栈跟踪警告信息输出

l         static java.lang.String--UNKNOWN_PREFIX—未知信息前缀

l         static java.lang.String--VM_CONTEXT_LOCALSCOPE—VM本地作用域开关,默认为false

l         static java.lang.String--VM_LIBRARY—本地Velocimacro库模板名称

l         static java.lang.String--VM_LIBRARY_AUTORELOAD—一个自动加载VM资源库的开关,用于开发阶段

l         static java.lang.String--VM_MESSAGES_ON—VM消息开关,默认为true

l         static java.lang.String--VM_PERM_ALLOW_INLINE—布尔值,默认为true,既是否允许内联(在模板里)宏定义

l         static java.lang.String--VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL—布尔值,默认为false,即是否允许内联(在模板里)宏定义替换现有的定义

l         static java.lang.String--VM_PERM_INLINE_LOCAL—是否强迫内联宏为本地的,默认为false

l         static java.lang.String--WARN_PREFIX—警告信息前缀

Context createContext(HttpServletRequest, HttpServletResponse)—该方法允许开发者创建他们自己的上下文对象,可以被用作私有merge()方法。

void setContentType( HttpServletRequest,HttpServletResponse)—默认情况下,handleRequest()方法将输出HTML格式的文本,但是,你可以更改成其他格式,比如XML,甚至是图片文件。

void mergeTemplate(Template, Context, HttpServletResponse)—如果你想自己控制输出来代替handleRequest()方法,那么你可以使用createContext()方法得到你自己的上下文,并且把它们和模板进行合并,之后通过handleRequest()方法输出给response对象。mergeTemplate()方法得到所有的三个对象,并且产生输出。

void requestCleanup(HttpServletRequest, HttpServletResponse, Context)—如果你想自己处理输出,你应该重载requestCleanup()方法,来处理任何最后的结果。默认情况下,这个方法并没有实现。

protected void error(HttpServletRequest, HttpServletResponse, Exception)—当在处理用户请求发生一个异常时,该方法将被调用。你可以重载这个方法来提供更多高级的错误处理能力。默认实现只是发送一个错误信息和一个堆栈跟踪信息给用户。

增加报表(report

我们的应用程序在此已经把注意力集中到产生一个HTML窗体格式的输出。但是,如果你不想要任何HTML样式的表,而是只想得到一个基于文本格式的所有数据库里CD的报表该怎么办?Ok,现在让我们考虑一下Listing 12.12里的Velocity模板。



Listing 12.12 A Velocity template that produces a text report.

Listing 12.12里的Velocity模板用于输出一个页面的头部,最多列出50张数据库里的CD,之后,产生另一个页面头部。在两个头部之间的CD数量可以改变,以适应于不同的输出。为了产生的完整的CD报表,你需要在主CD index.html页面增加一个按钮。下面给出这个按钮的代码:

<h3>Reports</h3>

<form action="http://localhost:8080/cd/cdVelocityHandler" method="post">

<input type="submit" name="submit" value="fullreport"> -

download 'report.txt' to your local system

</form>

这个新窗体在index页面上显示了一个名叫FullReport的按钮。当用户单击这个按钮的时候,控件被传递到cdVelocityHandler servlet(在Listing 12.11里定义的)。Listing 12.13的代码将用于处理这个新按钮。

else if (req.getParameter("submit").equals("fullreport")) {

try {

if (cdHome == null) {

context.put("message", "Sorry we had an error");

} else {

Collection cds = cdHome.findAllCDs();

context.put ("cds", cds);

try {

template = getTemplate("fullreport.vm");

} catch( Exception e ) {

e.printStackTrace();

}

}

} catch(Exception e) {

e.printStackTrace();

}

}

Listing 12.13 The control servlet report task.

除了配合CDRecordBean实体beanfindAllCDs查询外,这些代码没有什么特别的地方。新报表的查询见Listing 12.14

<query>

<query-method>

<method-name>findAllCDs</method-name>

</query-method>

<ejb-ql>SELECT o FROM CDTable o</ejb-ql>

</query>

Listing 12.14 The bean query.

新查询从CDTable数据库表从提出所有的行。一旦所有的行被提出,结果Collection对象就会被放入上下文中。最后,Velocity模板fullreport.vm被调用,用于输出这个查询的结果。输出的结果见Figure 12.5


Figure 12.5
The report output in a browser.

正如你所看见的一样,这个输出并不像我们希望的一样,只输出文本样式,而不需要HTML进行润色。如果我们想生成一个文本格式的文件并可以进行下载该怎么做?让我们来考虑一下Listing 12.15里的模板。


Listing 12.15
The Velocity template for text only.

Listing 12.15Velocity模板使用tab占位符来控制从数据库生成数据的输出格式。这也就意味着,ArtisTitle对应的字符串将拥有一个适当的格式和对齐方式,同时不再顾及这些字符串的长度。你肯定不想看到任何参差不齐的行排列样式。为了完成这个任务,在知道Artist的值后,你必须生成一个正确的tab个数。如果你看一下模板代码,你将会看到下面这行:

$value.artist$stringlength.tabs($value.artist)$value.title

这一个行是由下面三行代码构成的:

$value.artist

$stringlength.tabs($value.artist)

$value.title

$value.artist$value.title命令只简单为当前行产生将要显示的artisttitle数据。这个命令里最有趣的部分是$stringlength.tabs($value.artist)。这个$stringlength引用和一个放置在上下文的对象(使用StringLength类)关联。StringLength类代码见Listing 12.16

public class StringLength {

public StringLength(){}

public String tabs(String st) {

String s = new String();

for (int i=3;i>st.length()/5;i--)

s = s + ""t";

return s;

}

}

Listing 12.16 The StringLength class.

StringLength类只有一个简单的任务:暴露一个名叫tabs()的方法。这个方法的作用是计算并返回一个确定个数的tabs字符串,以用于排列artisttitle的值。艺术家的名称被传递给tabs()方法,之后确定数量的tabs被返回。

现在,你的Velocity模板已经能够返回正确的输出,OK,让我们考虑一下如何让你的控件Servlets生成一个文件,以便用户下载。Listing 12.17的代码显示了Full-Report按钮的控件Servlets代码。

else if (req.getParameter("submit").equals("fullreport")) {

try {

if (cdHome == null) {

context.put("message", "Sorry we had an error");

} else {

Collection cds = cdHome.findAllCDs();

context.put ("cds", cds);

try {

res.setContentType("APPLICATION/OCTET-STREAM");

res.setHeader("Content-Disposition","attachment;

filename=report.txt");

template = getTemplate("fullreport.vm");

} catch( Exception e ) {

e.printStackTrace();

}

}

} catch(Exception e) {

e.printStackTrace();

}

}

Listing 12.17 The servlet code for downloading the report.

正如你所看到的一样,findAllCDs查询被用于从数据库中提取所有的CD信息,同时,模板fullreport.vm被用于输出。其中增加了两个新命令:

res.setContentType("APPLICATION/OCTET-STREAM");

res.setHeader("Content-Disposition","attachment; filename=report.txt");

这两个命令用于告诉用户浏览器,从<form>返回的信息是一个名叫report.txt的文件,这个信息将导致出现一个下载对话框,用于用户下载文件。Figure 12.6显示了使用这个应用程序产生的文件下载情况。


Figure 12.6
The report output.

本章小节和下章介绍

在这一章里,我们介绍了一种可能对开发者有用的混合VelocityServlets的技术。在下一章里,我们将演示如何扩展Velocity驱动的站点,以适当国际化需要的技术。

posted on 2008-10-24 16:35 KINGWEE 阅读(815) 评论(0)  编辑  收藏 所属分类: Velocity


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


网站导航: