精通Struts技术第一章
第一章:
Jakarta Struts
项目的介绍和它的支持组件
我们所有的章节的Web服务器都是基于Jakarta Tomcat容器。在本章结束的时候,通过运行你建立的Struts应用程序,你可以对Struts的框架机制有个大致的了解
The Jakarta Struts项目
The Jakarta Struts
项目是由
Apache Software Foundation
发起的开源项目,它在
java
服务器端实现了
MVC
(
Model-View-Controller
)设计模式。
The Jakarta Struts
项目最初是由
Craig McClanahan
在
2000
年五月创建的,但后来被开源组织所接收。
The Jakarta Struts
作为一个开源框架的主要意图是为了在创建
WEB
应用时能比较容易的分离表示层和业务数据层。自从它诞生以来收到大量开发人员的支持,并很快的成为开源社区中一个重要的成员。
理解MVC设计模式
为了更深刻的理解
Struts
框架,你必须先理解
MVC
设计模式,
Struts
技术的就是基于
MVC
设计模式的。
MVC
设计模式起源于
Smalltalk
语言,它由以下三个部分组成:模型(
model
)
,
视图(
view
),控制器(
Controller
)
.
表
1.1
定义了这些组件。
组件
|
描述
|
模型(
model
)
|
封装数据对象。模型用来封装和显示数据对象。
|
视图(
view
)
|
作为模型的显示,它表示数据对象的当前状态
|
控制器(
Controller
)
|
定义对用户的输入执行相应操作的接口,它用来操作模型(
model
)和数据对象
|
使用
MVC
的好处包括如下:
可靠性:
表示层和业务层别分离,这样就允许你更改你的表示层代码而不用重新编译你的模型(
model
)和控制器(
Controller
)代码
高重用和可适应性
: MVC
模式允许你使用各种不同样式的视图来访问同一个服务器端的代码。它包括任何
WEB
(
HTTP
)浏览器或则无线浏览器
(WAP)
。
较低的生命周期成本:
MVC
使降低开发和维护用户接口的技术含量成为可能。
快速的部署
:开发时间会得到相当大的缩减,它使程序员(
java
开发人员)集中精力于业务逻辑,界面程序员(
HTML
和
JSP
开发人员)集中精力于表现形式上。
可维护性
:
分离表示层和业务逻辑层也使得基于
Struts
的
Web
应用更易于维护和修改。
The Struts
架构图
在这章节,我们简要的画出一幅和
MVC
模式对应的
STRUTS
框架图
1.1
:
图1.1是Struts框架下应用程序请求流通过的路径。这个处理过程由5个基本的步骤组成。
下面是
处理步骤的描述。
1.
由显示视图产生一个请求。
2.
请求被
ActionServlet
(控制器)接收,它在
struts-config.xml
文件中寻找请求的
URI
,找到对应的
Action
类后,
Action
类执行相应的业务逻辑。
3.
Action类执行建立在模型组件基础上的业务逻辑,模型组件是和应用程序关联的。
4.
一旦Action类处理完业务逻辑,它把控制权返回给
ActionServlet
。,
Action
类提供一个键值作为返回的一部分,它指明了处理的结果。
ActionServlet
使用这个键值来决定在什么视图中显示
Action
的类处理结果。
5.
当
ActionServlet
把
Action
类的处理结果传送到指定的视图中,请求的过程也就完成了。
模型(
The Model
)
Struts
框架没有提供特定的模型组件,因此我们不会整章介绍模型组件,但我们会把它使用到我们的例子中去。
视图(
The View
)
Struts
框架中
视图组件对应于一个简单的
JSP
文件,这个
JSP
文件包含了
Struts
定义的标签,下面的代码片段是一个简单的
Struts
视图:
<%@page language="java">
<%@taglib uri="/WEB-INF/struts-html.tld" prefix="html">
<html:form action="loginAction.do"
name="loginForm"
type="com.wiley.loginForm" >
User Id: <html:text property="username"><br/>
Password: <html:password property="password"><br/>
<html:submit />
</html:form>
如你看到的那样,几个
JSP
的标签是
JSP
文件中的重点。这些标签在
Struts
框架中定义,它使
struts
应用项目和控制器之间实现松耦合。在第三章我们会建立一个真正可运行的
struts
视图,第五章中会对视图进行更详细的介绍。
控制器(
The Controller
)
控制器是
Struts
框架中的中枢,它由
org.apache.struts.action.ActionServlet
这个
servlet
来贯彻和执行的。这个
org.apache.struts.action.ActionServlet
接收所有客户端的请求,并把请求委派到指定的
Action
类
(
用户扩展自
org.apache.struts.action)
。
ActionServlet
委派请求是基于客户端传入的
URI
。一旦
Action
类完成处理,
ActionServlet
根据
Action
返回的键值来决定在什么视图中显示
Action
的类处理结果。
ActionServlet
类似于一个创建
Action
对象的工厂,由
Action
对象去执行应用中实际的业务逻辑。控制器是
Struts
框架中最重要的部分。我们会在第三章和第四章对控制器进行更详细的探讨。
Web应用(WebApplications)
所有的Web应用被包含在一个目录结构中,首先你要在Web服务器下建立如下目录:
目录
|
内容
|
wileyapp
|
这是Web应用的根目录,所有的JSP和HTML文件都在这个目录下。
|
/wileyapp/WEB-INF
|
这个目录包含了所有除根目录外需要的资源,
包括
WEB
应用部署信息。注意:
WEB-INF
目录下的不是公共文件,无法直接服务于客户端。
|
/ wileyapp/WEB-INF/classes
|
存放了所有的
servlet
类或实用类。
|
/ wileyapp/WEB-INF/lib
|
包含所有
WEB
应用中要用到的后缀为
JAR
的包文件
|
图1.2
如果你实用的是
Tomcat
服务器,你的缺省根目录为
<CATALINA_HOME>/webapps/
,在
<CATALINA_HOME>/webapps/
下建立
图1.2所示目录
注意:WEB服务器允许你编译你的类并存放到
/WEB-INF/classes
或
/WEB-INF/lib
下,但
/WEB-INF/classes
的类将被优先载入,即若你编译的类在两个目录下都存在,起作用的只有
/WEB-INF/classes
目录下的类。
Web
应用的部署描述
Web应用的核心是部署描述。部署描述存放在
/<SERVER_ROOT>/applicationname/WEB-INF/
下的
web.xml
文件中,它描述了所有
Web应用要使用的组件。如果你使用图1.2的目录结构,那么你的部署描述位于
/<SERVER_ROOT>/wileyapp /WEB-INF/web.xml
文件中。
部署描述中包括如下元素:
·
ServletContext init parameters
·
Localized content
·
Session configuration
·
Servlet/JSP definitions
·
Servlet/JSP mappings
·
Tag library references
·
MIME type mappings
·
Welcome file list
·
Error pages
·
Security information
下面的代码片段是部署描述的一个例子,它定义了一个简单的
servlet
。
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>SimpleServlet</servlet-name>
<servlet-class>com.wiley.SimpleServlet</servlet-class>
</servlet>
</web-app>
打包Web应用
标准的
Web应用都被打包成后缀为war的文件,你可以使用
Java
打包命令来创建
WAR
文件:
jar cvf wileyapp.war
现在你就可以用
wileyapp.war
文件
发布你WEB应用了。
Tomcat容器
Tomcat
是是基于
JAVA
的开源
WEB
应用服务器,我们的例子都以
Tomcat
为
WEB
应用服务器。在开始我们的学习前你需要下载
Tomcat4
地址:
http://jakarta.apache.org/builds/jakarta-tomcat-4.0/release/v4.0.6/src/jakarta-tomcat-4.0.6-src.zip
JDK1.4
地址:
http://java.sun.com/webapps/download/Redirect/68949517/5847479377282807053505607246086069333228071972022813600060132859339008063305596058473206-3865/j2sdk-1_4_1_02-s1studio_ce-4u1-bin-windows.exe
操作系统为
Win2000
2
.在系统变量中设置
JAVA_HOME
= root
3
.
在系统变量中
设置
CATALINA_HOME
>
= root
4
.在
系统变量中
设置
CLASSPATH= root/lib/dt.jar
;
jar;root/lib/tools.jar
;
root/jre/rt.jar;
6.
在
系统变量中
PATH
下添加
root/bin
;
注意:
root
为你的安装目录
你若是能看到缺省的
Tomcat
主页面,表明你的配置成功了。如果未成功请查看你的
JDK
是否已经安装,或者
JDK
路径是否设置正确。
下一章将大致讲述
JSP
和
servlets
,若你对于这方面的技术已经了解,你可以跳过下一章节。
精通struts技术第二章
<jsp:include>
用于在JSP中包含静态或动态的WEB组件。语法如下:
<jsp:include page="urlSpec" flush="true">
<jsp:param ... />
</jsp:include>
表2.9描述了<jsp:include>的属性:
属性
|
说明
|
page
|
要包含的资源的地址,基于URL
|
flush
|
指明是否缓冲。
|
通过一个例子来对include进行说明:
列表 2.13 include.jsp.
----------------------------------------------------------------
<html>
<head>
<title>Include Example</title>
</head>
<body>
<table width="100%" cellspacing="0">
<tr>
<td align="left">
<jsp:include page="header.jsp" flush="true">
<jsp:param name="user"
value=’<%= request.getParameter("user") %>’ />
</jsp:include>
</td>
</tr>
</table>
</body>
</html>
------------------------------------------------------------
include.jsp文件包含了一个简单的JSP文件header.jsp,文件显示如下:
列表 2.14. header.jsp.
------------------------------------------
<%
out.println("<b>Welcome: </b>" +
request.getParameter("user"));
%>
------------------------------------
header.jsp寻找名称为user的参数并输出“welcome……”字符串。复制JSP文件到:
<CATALINA_HOME>/webapps/wileyapp/ 目录,在浏览器中输入地址.:
http://localhost:8080/wileyapp/include.jsp?user=Bob
<jsp:forward>
在当前的WEB应用中告诉JSP引擎重定向当前请求到另外可用的资源上,包括静态资源,servlets,JSP等。<jsp:forward>能有效的终止当前执行的JSP。
注意:<jsp:forward>能包含<jsp:param>子属性,这个子属性作为从定向的目标资源。
语法如下:
<jsp:forward page="relativeURL">
<jsp:param .../>
</jsp:forward>
表2.10对<jsp:forward>的属性进行描述:
列表:2.15使用<jsp:forward>的例子:检验请求参数并且重定向请求到包含对应参数的JSP页面。
列表2.15 forward.jsp.
-----------------------------------------------------------------
<html>
<head>
<title>JSP Forward Example</title>
</head>
<body>
<%
if ( (request.getParameter("role")).equals("manager") ) {
%>
<jsp:forward page="management.jsp" />
<%
}
else {
%>
<jsp:forward page="welcome.jsp">
<jsp:param name="user"
value=’<%=request.getParameter("user") %>’ />
</jsp:forward>
<%
}
%>
</body>
</html>
-------------------------------------------
forward.jsp简单的检验请求参数的角色类型,然后重定向请求到相应的页面。
目标资源文件之一:welcome.jsp.
------------------------------------------------
<html>
<!-- Set the scripting language to java -->
<%@ page language="java" %>
<HTML>
<HEAD>
<TITLE>Welcome Home</TITLE>
</HEAD>
<BODY>
<table>
<tr>
<td>
Welcome User: <%= request.getParameter("user") %>
</td>
</tr>
</table>
</body>
</HTML>
----------------------------------------------------------
目标资源文件之二:management.jsp
------------------------------------------------------
<html>
<!-- Set the scripting language to java -->
<%@ page language="java" %>
<HTML>
<HEAD>
<TITLE>Management Console</TITLE>
</HEAD>
<BODY>
<table>
<tr>
<td>
Welcome Manager: <%= request.getParameter("user") %>
</td>
</tr>
</table>
</BODY>
</HTML>
复制三个JSP文件到<CATALINA_HOME>/webapps/ wileyapp/目录下,在浏览器中输入:
你可以通过修改输入的角色来重定向到不同的页面。
<jsp:plugin>
导致下载指定的applet和JavaBeans组件,并同步执行业务。
.<jsp:plugin>语法如下:
<jsp:plugin type="pluginType"
code="classFile"
codebase="relativeURLpath">
<jsp:params>
</jsp:params>
</jsp:plugin>
表2.11说明了<jsp:plugin>的属性:
属性
|
说明
|
type
|
说明plug-in包含的类型(比如applet)
|
code
|
被plug-in执行的类名称
|
Codebase
|
寻找代码的绝对或相对路径
|
小结:
在这一章我们讲述了Struts技术的两个基础:servlet和JSP。我们试验了它们的结构和组件。了解了JSP和servlet 技术是如何在WEB应用中被装配使用的。下一章,我们将真正开始讲述Strutst技术。
第三章:开始学习Struts
在这一章,我们将开始学习Struts。在开始之前,我们必须先安装和配置Struts应用。我们将创建一个简单的应用来显示Struts是如何工作的。
这一章我们将提供一个Struts应用来让你快速入门。
获取和安装Jakarta Struts项目
在开始你的Struts开发前,我们需要获取最新版本的Struts包。目前struts包为1.1
.地址如下:http://cvs.apache.org/builds/jakarta-struts/nightly/jakarta-struts-20031010.zip
一旦你获取了版本后,你在部署Struts应用前需要完成下面的预备工作。
1. 创建一个应用目录<CATALINA_HOME>/webapps/wileystruts
2. 复制struts的JAR文件到<CATALINA_HOME>/webapps/wileystruts/WEB-INF/lib目录下,jar文件列表如下:
struts.jar ¨
commons-beanutils.jar ¨
commons-collections.jar ¨
commons-dbcp.jar ¨
commons-digester.jar ¨
commons-logging.jar ¨
commons-pool.jar ¨
commons-services.jar ¨
commons-validator.jar ¨
3.在<CATALINA_HOME>/webapps/wileystruts/WEB-INF/下创建web.xml,在文件中添加如下代码:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application
2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
</web-app>
4.创建一个strut-config.xml文件,复制到
<CATALINA_HOME>/webapps/wileystruts/WEB-INF/ 目录下。
struts-config.xml是Struts应用的部署描述文件。它用来把MVC组件组合在一起。
它通常被放置在
<CATALINA_HOME>/webapps/ webappname/WEB-INF/目录下,我们将会频繁的使用到它。空的struts-config.xml如下:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config
PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<message-resources
parameter="wiley.ApplicationResources"/>
</struts-config>
注意:如果你使用Struts 1.1 b1版本,在struts-config.xml文件中你需要添加<message-resources />元素。现在我们只是简单的创建struts-config.xml文件,对于每个元素的详细介绍我们将放在第六章“国际化你的struts应用”。在这章我们将创建struts应用中所有必须的组件。当你开始设计和开发你的Struts应用时,你必须安装和培植你的struts组件。在下一节,我们将会说明那些步骤是必须完成的。
创建你的第一个Struts应用
你已经完成了struts包的下载和安装,现在可以开发Struts应用了。我们的应用由查询股票号码的简单的JSP页面来完成,它将返回被查询股票的价格。我们会通过这个例子来说明在创建Struts应用中必须执行哪些步骤。因为Struts技术来源于MVC设计模式,所以在所有基于Struts的开发中,你可以遵循MVC模式这种标准来进行处理。处理方式为:从视图开始,控制器对象操纵模型组件来为视图服务。这个处理过程通过下面步骤来实现:
1.定义并创建实现某一功能的相关视图,它们是应用中的用户接口。在struts-config.xml这个struts配置文件中所有的ActionForm 就是我们要创建的视图。
2.创建控制器组件。
3.在struts-config.xm文件中定义视图,控制器之间的关系。
4.在web.xml文件中配置启动struts的一些必要信息。
5.启动应用。
上述步骤只是对Struts开发做了概要描述,在接下来的章节我们将会对这些步骤进行比较细致的描述。
创建视图
在创建struts应用的视图时,我们会先创建一个包含Struts标签的JSP文件。
当前有三个主要的struts标签,Bean, HTML和Logic。在第5章我们会集中关注在这几个标签库上,但这章我们在视图中主要关注于HTML标签库。在开始我们的应用时,我们首先要需要描述视图接口。在下面的应用中有两个视图:index.jsp和quote.jsp。
Index视图
Index视图有index.jsp文件来表现,是我们第一个看到的视图。它用来查询股票和提交
请求到相应的Action中。Index.jsp源文件如下:
------------------------------------------------------------
Listing 3.1: index.jsp.
<%@ page language="java" %>
<%@ taglib
uri="/WEB-INF/struts-html.tld"
prefix="html" %>
<html>
<head>
<title>Wiley Struts Application</title>
</head>
<body>
<table width="500"
border="0" cellspacing="0" cellpadding="0">
<tr>
<td> </td>
</tr>
<tr bgcolor="#36566E">
<td height="68" width="48%">
<div align="left">
<img src="images/hp_logo_wiley.gif"
width="220"
height="74">
</div>
</td>
</tr>
<tr>
<td> </td>
</tr>
</table>
<html:form action="Lookup"
name="lookupForm"
type="wiley.LookupForm" >
<table width="45%" border="0">
<tr>
<td>Symbol:</td>
<td><html:text property="symbol" /></td>
</tr>
<tr>
<td colspan="2" align="center"><html:submit /></td>
</tr>
</table>
</html:form>
</body>
</html>
如你看到的index视图,除了form标签外,它就象一个包含数据集合的HTML页面。在HTML页面中我们把FORM标签改为用Struts标签<html:form />来替换。
这个标签和它的子标签封装了struts form的处理。Form标签的属性列表如下:
属性
|
描述
|
action
|
Form被提交到的URL地址。这个属性用来在struts-config.xml配置文件中查找对应的action。
这个属性在我们的例子中是“Lookup”。
file, which we will describe later in this section. The
value used in our example is Lookup, which will map
to an ActionMapping with a path attribute equal to
|
name
|
ActionForm的识别键。在这个例子中的值为LookupForm。
ActionForm是Struts中用来封装客户端数据的JavaBean对象。它的主要目的是审查视图到控制器组件的数据。我们会在本章的后面讨论LookupForm。
|
Type
|
请求的类名的全称,这个例子中的值为wiley.LookupForm。它封装了客户端输入的数据。
|
<html:form />标签是另外两个标签的父类。第一个是<html:text /> tag.。这个标签和HTML中的<input type=”text”..>意义是一样的。只有一点不同的地方那就是property属性。它的值是唯一的,和ActionForm类中对应的成员变量是对应的。
第二个标签是<html:submit />,它只是简单仿效HTML的submit按钮的功能。
ActionForm 类通过使用<html:form />标签来创建一个实例,并把<html:text />标签的值存储到实例中。实例最后被存储到一个session对象当中。
一旦ActionForm实例被存储了相应的数值之后,action通过<html:form />来调用和审核存储在ActionForm上的数据。
1.为了使用前面提到的标签,你必须在 wiley这个s应用的Web.xml文件中添加对应的标签引用。下面的代码段就是添加在web.xml中的标签元素:
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
2.你必须复制struts-html.tld文件到<CATALINA_HOME>/webapps/wileystruts/ WEB_INF/目录下。
注意:上面的这两个步骤在部署所有struts应用中都会用到,唯一的不同就是部署时TLD文件的不同(表示不同的标签库)。另外的标签库我们会在第五章进行讨论。
第三章:开始学习Struts(2)
ActionForm
ActionForm用来保存视图中表单输入参数的实际数值。如我们在前面章节提到的,当一个<html:form />标签被提交,Struts框架把对应的数据组装到ActionForm实例中。Struts框架通过JavaBean影射,因此ActionForm中的成员变量必须遵守JavaBean的命名规则。举例如下:
private String symbol;
public void setSymbol(String symbol);
public String getSymbol();
在这个例子中有一个简单成员变量
symbol
。在获取或设置成员变量的数值时必须带有前缀
set
和
get
,在
set
和
get
后面的成员变量的第一个字符必须为大写。列表
3-2
是
ActionForm
的源代码
LookupForm.java.
--------------------------------------------------------------------------------
package wiley;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
public class LookupForm extends ActionForm {
private String symbol = null;
public String getSymbol() {
return (symbol);
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public void reset(ActionMapping mapping,
HttpServletRequest request) {
this.symbol = null;
}
}
---------------------------------------------------------
在这个类中没有特殊的东西,它只是简单的继承
org.apache.struts.action.ActionForm
,以及每个
ActionForm
实例对每个成员变量必须实现的
get,set
方法。在这个
ActionForm bean
中有一个特殊的
reset()
方法。每一个使用
LookupForm
的请求都会执行
reset()
方法。这个方法的目的是为了重置
LookupForm
的成员变量,以便下一个应用使用。
注意:
reset()
方法
ActionMapping
类来被引用。关于
ActionMapping
类我们暂时可以忽略,将在第
4
,
5
章中有详细的描述。
编译
LookupForm
类,移到
<CATALINA_HOME>/webapps/wileystruts/WEB-INF/classes/wiley
目录下,在
<CATALINA_HOME>/webapps/wileystruts/WEB-INF/struts-config.xml
文件中添加
<form-bean name="lookupForm" type="wiley.LookupForm"/>
这是为了让
Struts
应用知道
LookupForm
如何被引用。
Quote
视图
我们最后的视图是
quote.jsp
。视图在用户成功的寻找到股票后显示。它是没有特殊
struts
功能的非常简单的
JSP
页面。列表
3-3
quote.jsp.
----------------------------------------------------
<html>
<head>
<title>Wiley Struts Application</title>
</head>
<body>
<table width="500" border="0" cellspacing="0" cellpadding="0">
<tr>
<td> </td>
</tr>
<tr bgcolor="#36566E">
<td height="68" width="48%">
<div align="left">
<img src="images/hp_logo_wiley.gif"
width="220" height="74">
</div>
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>
Current Price : <%= request.getAttribute("PRICE") %>
</td>
</tr>
<tr>
<td> </td>
</tr>
</table>
</body>
</html>
-----------------------------------------------
上面的
JSP
中包含简单一个简单的代码行,它用
HttpServletRequest
获取特定的股票的价格。这个价格数据通过
Action
对象中的
HttpServletRequest
实例来设置。
创建控制组件
在
Struts
应用中,控制器由两个组件组成。这两个组件是
org.apache.struts.action.ActionServlet
和
org.apache. struts.action.Action
类。在大多数的
Struts
应用只存在一个
org. apache.struts.action.ActionServlet
实现(实例),但存在多个
org.apache.struts.action.Action
实现(多个继承实例)。
org.apache.struts.action.ActionServlet
用来控制客户端请求和决定哪个
org.apache.struts.action.Action
实例来处理请求。在我们创建的简单应用中,缺省的
ActionServlet
已经足以满足我们的应用需求了,你不需要再实现特定的
org.apache.struts.action.ActionServlet
。即使需要扩展
ActionServlet
也很容易。我们会在第四章中详细介绍。
控制器的第二个组件是
org.apache.struts. action.Action
,和
ActionServlet
不同的是
Action
类在你的应用中必须扩展。这个类是你业务逻辑的开始部分。
例如我们寻找指定的股票的价格。我们会创建一个从
org.apache.struts.action.Action
扩展的
LookupAction
。代码如下:
LookupAction.java
------------------------------------------------------------
package wiley;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
public class LookupAction extends Action {
protected Double getQuote(String symbol) {
if ( symbol.equalsIgnoreCase("SUNW") ) {
return new Double(25.00);
}
return null;
}
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
Double price = null;
// Default target to success
String target = new String("success");
if ( form != null ) {
// Use the LookupForm to get the request parameters
LookupForm lookupForm = (LookupForm)form;
String symbol = lookupForm.getSymbol();
price = getQuote(symbol);
}
// Set the target to failure
if ( price == null ) {
target = new String("failure");
}
else {
request.setAttribute("PRICE", price);
}
// Forward to the appropriate View
return (mapping.findForward(target));
}
}
-----------------------------------------------------------------
我们注意到
LookupAction类有两个方法:getQuote() and execute()。getQuote()方法会返回一个价格(如果提交的股票代码为“
SUNW
”)。
execute()
方法是
LookupAction
的主函数,在所有的
Action
类中必须被实现(原文可能有误,在
strut1.1
上不是必须实现,可以使用
perform()
方法替代)。
第二章:Servlet和JSP结构
在这章我们讨论两个问题JSP和servlets技术,它们是Struts框架的基础。我们描述的servlet结构包括它的生命周期,上下文环境(ServletContext)及WEB应用之间的关系。一旦你对Servlet有了比较透彻的了解,我们将集中讨论Struts框架中的视图即JSP页面。本章的目的是让你对JSP和Servlet技术有个大致的了解。在本章结束的时候你会清楚的知道为何Servlet和JSP技术适合于WEB开发。
Java Servlet 结构
Java servlet是平台独立的WEB应用组件,Servlets和客户端协作是通过request/response来进行处理的。图2.1为处理图
servlet结构有两个包组成:javax.servlet 和 javax.servlet.http。
javax.servlet包含了被用来实现和扩展的通用接口和类。
javax. servlet.http是被用于特定的HTTP协议的。
Servlet接口被定义了五个方法。其中比较重要的有三个:
1.init()方法,用于初始化一个Servlet;
2.service方法,用于接受和响应客户端的请求。
3.destroy()方法,执行清除占用资源的工作。
这些是servlet生命周期方法。我们会在后面的章节解说这些方法。所有的servlet都必须实现javax.servlet.Servlet接口,不管是直接或者是间接的。图2.2是对象图,给出了servlet的层次框架图
GenericServlet和HttpServlet类
HttpServlet从GenericServlet扩展而来,GenericServlet实现了Servlet接口。当开发你的servlets时,你通常需要扩展GenericServlet的子类。你必须实现service()方法。GenericServlet.service()方法已经被定义为抽象方法,定义的原型如下:
public abstract void service(ServletRequest request,
ServletResponse response) throws ServletException, IOException;
ServletRequest,ServletResponse这两个参数通过service()方法来传递。ServletRequest对象用来把得到的信息传送到servlet,ServletResponse对象用来把servlet产生的数据传送回到客户端。
对比GenericServlet和HttpServlet,HttpServlet不需要实现service()方法,HttpServlet类已经为你实现了service()方法。方法原型如下:
protected void service(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException;
当HttpServlet.service()方法被执行,它读取存储在request中的方法类型的值来决定哪个方法被执行。这里是你想重载的方法:如果方法类型为GET,就调用doGet()方法。如果为POST就调用doPost()方法。主要使用的就是这两个方法。另外的五个方法不是经常使用。因此我们比较关注的也就是这两个方法。
Servlet生命周期
Servlet生命周期有一个合理的逻辑顺序。javax.servlet.Servlet接口声明了周期方法。
这些方法就是init(), service(),destroy()。执行顺序分为三个处理步骤:
1.
init()方法用来把servlet导入和初始化。这个方法在servlet被预加载或在第一次请求时执行。
2.
Servlet处理0个或多个请求。Servlet对每个请求都用service()方法来处理。
3.
当WEB应用声明Servlet被关闭,Servlet被销毁,垃圾收集器对资源进行收集。用destory方法来关闭Servlet。
init() 方法
init()方法是servlet生命周期的开始。这个方法在servlet被实例化后立即执行,它只被调用一次,用来创建和初始化请求中用到的资源。
init() 方法的显示如下:
public void init(ServletConfig config) throws ServletException;
ServletConfig参数对象是成员变量,它在后面会被用到。一个比较通用的做法是调用超类的init()方法super.init()。如果因为一些原因servlet不能初始化请求所要求的资源就会抛出ServletException
service() 方法
service()方法 处理来自客户端的所有请求。
service()方法表示如下:
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
service()方法有两个参数:
ServletRequest:封装来自客户端的请求信息
ServletResponse:返回客户端的信息
通常情况下你不会实现这个方法,除非你扩展GenericServlet抽象类。
经常实现service()这个方法的是HttpServlet类。HttpServlet通过扩展GenericServlet来实现Servlet接口。HttpServlet支持HTTP/1.1。
destroy()方法
这个方法用于结束servlet生命周期。当一个WEB应用被关闭,destroy()方法被执行,
这时在init()方法中被创建的资源被释放。下面是destroy()方法的代码片段:
public void destroy();
创建一个Servlet
现在我们已经基本了解servlet是如何工作了,我们将创建一个非常简单的servlet应用。
它的主要功能是为了处理客户端请求,并在客户端输出客户地址。
在完成代码后我们将编译代码并部署它。下面为SimpleServlet.java类的源代码:
package chapter2;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class SimpleServlet extends HttpServlet {
public void init(ServletConfig config)
throws ServletException {
// Always pass the ServletConfig object to the super class
super.init(config);
}
//Process the HTTP Get request
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
//Process the HTTP Post request
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>Simple Servlet</title></head>");
out.println("<body>");
// Outputs the address of the calling client
out.println("Your address is " + request.getRemoteAddr()
+ "/n");
out.println("</body></html>");
out.close();
}
}
现在我们可以查看SimpleServlet代码的细节部分了。我们来看它的三个覆盖方法:
init() ·
doGet() ·
doPost() ·
每个方法的细节:
init()方法
SimpleServlet类首先定义了init()方法,它以ServletConfig对象为参数调用父类的init()方法:super.init(config)
注意:由SimpleServlet的父类GenericServlet来实际处理ServletConfig 对象,你可能也注意到了init()方法并没有创建任何资源,这也是为什么SimpleServlet中不执行destroy()方法的原因了。
doGet() 和 doPost() 方法
所有的业务逻辑都在这两个方法中执行。在这个例子中
doGet()方法只是简单的调用doPost()方法。doGet()方法只在GET请求时被执行,doPost()方法也一样,只在Post请求时被执行。两个方法的传入参数是HttpServletRequest和HttpServletResponse对象。HttpServletRequest对象包含的是来自客户端的信息,HttpServletResponse包含的是返回客户端的信息。在doPost()方法中第一步是设置返回的内容类型:代码片段如下:
response.setContentType("text/html");
在输出流中它必须被最先设置,在例子中我们设置为text/html.
下一步获取PrintWriter对象,这是通过ServletResponse对象的getWriter()方法得到的。PrintWriter对象可以让我们写入输出流后发送到客户端显示,这个步骤代码片段如下:
PrintWriter out = response.getWriter();
一旦你获取了PrintWriter对象后,我们将往客户端输出信息,信息包含HTML标记,这是输出到客户端的格式,下面的几行代码显示了如何处理输出:
out.println("<html>");
out.println("<head><title>Simple Servlet</title></head>");
out.println("<body>");
// Outputs the address of the calling client
out.println("Your address is " + request.getRemoteAddr()
+ "/n");
前面部分代码说明你的输出格式为HTML,下面这段代码你可能会有些疑问:
// Outputs the address of the calling client
out.println("Your address is " + request.getRemoteAddr() + "/n");
这段代码调用了HttpServletRequest对象的getRemoteAddr()方法来获取客户端的IP地址,并将它发送到客户端。HttpServletRequest对象可以获取许多HTTP协议下的客户端的信息。如果你想更深入的了解HttpServletRequest和HttpServletResponse对象,可以访问SUN公司的网站:http://java.sun.com/products/servlet/
创建和部署Servlet
我们需要创建一个WEB应用来存放Servlet,并且编译和部署Servlet到WEB应用中去。步骤描述如下:
1.创建名称为wileyapp的WEB应用,如我们在第一章中所说的那样。.
2.编译SimpleServlet.java文件,拷贝SimpleServlet.class文件到<CATALINA_HOME>/webapps/wileyapp/WEB-INF/classes/chapter2/目录
4.你可以启动你的WEB Server,执行SimpleServlet并查看结果。在浏览器中输入如下地址http://localhost:8080/wileyapp/servlet/chapter2.SimpleServlet
你可以看到输出:Your address is 127。0。0。1
注意:在地址中包含了“/servlet”是为了告诉webServer你要执行的是一个
servlet
ServletContext
ServletContext是定义在javax.servlet包中的对象。它定义了用于WEB应用中的服务器端组件关联servlet容器的方法集合。
ServletContext经常被用于存储对象的区域,这些对象在WEB应用中的所有的服务器端组件中使用。你可以把ServletContext当作在WEB应用中共享的存储区域。把一个对象放置到ServletContext中时,它存在于WEB应用的整个生命周期中,除非它被明确的删除或替换。在ServletContext中定义了四个方法来实现存储区的共享功能。
表2.1描述了四个方法:
方法名
|
描述
|
setAttribute(String name,Object obj)
|
通过名称绑定一个对象并存储对象到当前ServletContext。如果指定的名称已经被使用过,这个方法会删除旧对象绑定为新对象。
|
getAttribute(String name)
|
返回指定名称的对象,如果名称不存在返回null。
|
removeAttribute(String name)
|
从ServletContext中删除指定名称的对象
|
getAttributeNames()
|
返回在ServletContext中的指定名称的对象集合
|
Web 应用和ServletContext的关系:
ServletContext 在WEB应用中充当容器的角色。在WEB应用中只有一个ServletContext 实例,Java Servlet规范指定ServletContext作为所有servlet 的容器。The ServletContext acts
为了了解它在WEB组件中的关系,我们将使用一个Servlet和一个JSP来说明。
在这个Servlet中。我们可以看到在ServletContext中存储的一个对象,这个对象在所有的服务端组件中都可使用。
列表2.2显示了servlet的源代码:
Listing 2.2 ContextServlet.java.
----------------------------------------------------------------------------------
package chapter2;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class ContextServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html";
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Get a reference to the ServletContext
ServletContext context = getServletContext();
// Get the userName attribute from the ServletContext
String userName = (String)context.getAttribute("USERNAME");
// If there was no attribute USERNAME, then create
// one and add it to the ServletContext
if ( userName == null ) {
userName = new String("Bob Roberts");
context.setAttribute("USERNAME", userName);
}
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>Context Servlet</title></head>");
out.println("<body>");
// Output the current value of the attribute USERNAME
out.println("<p>The current User is : " + userName +
".</p>");
out.println("</body></html>");
}
public void destroy() {
}
}
如你看到的ContextServlet,你注意到它执行了下面的步骤:
1.首先通过getServletContext()方法得到ServletContext的一个引用。
ServletContext context = getServletContext();
2.一旦你得到了ServletContext的引用,它将通过getAttribute()方法去获取绑定的名称的对象。绑定名称为USERNAME:.
String userName =(String)context.getAttribute("USERNAME");
3.检验返回的对象是否正确,如果getAttribute()方法返回null,说明没有对象绑定到名称USERNAME上。如果对象没有找到,它将创建一个,并添加到ServletContext中。绑定名称USERNAME,使用setAttribute()方法
// If there was no attribute USERNAME, then create
// one and add it to the ServletContext
if ( userName == null ) {
userName = new String("Bob Roberts");
context.setAttribute("USERNAME", userName);
}
4.通过PrintWriter.println(),传送获取的数据到输出流中。
// Output the current value of the attribute USERNAME
out.println("<p>The current User is : " +
userName + ".</p>");
编译你的servlet,并把编译后的class文件放到<CATALINA_HOME>/webapps/wileyapp/WEB-INF/classes/chapter2/目录下,这样servlet被部署到WEB应用中了。
在我们即将写的JSP中会和上面的servlet有很多相似之处,但是有两个不同的地方:ServletContext是在JSP脚本中本访问(这个问题我们将在本章稍后讨论)。
如果JSP中没有发现USERNAME属性,它不能添加一个新的。
代码实现的功能在本质上是一样的,只不过在JSP中。你可以查看源代码:
列表 2.3: Context.jsp.
<HTML>
<HEAD>
<TITLE>
Context
</TITLE>
</HEAD>
<BODY>
<%
// Try to get the USERNAME attribute from the ServletContext
String userName = (String)application.getAttribute("USERNAME");
// If there was no attribute USERNAME, then create
// one and add it to the ServletContext
if ( userName == null ) {
// Don’t try to add it just, say that you can’t find it
out.println("<b>Attribute USERNAME not found");
}
else {
out.println("<b>The current User is : " + userName +
"</b>");
}
%>
</BODY>
</HTML>
注意:Context.jsp中,我们使用了两个固有对象,application(用于引用ServletContext),out(用于输出流到客户端)。在这章的后面我们将讨论这两个对象。现在复制Context.jsp文件到<CATALINA_HOME>/webapps/wileyapp/,重新启动Tomcat;在浏览器中输入地址:
你会看到输出:Attribute USERNAME not found
Context.jsp没有发现USERNAME属性。如果你输入如下地址:
http://localhost:8080/wileyapp/servlet/chapter2.ContextServlet
会输出:The current User is Bob Roberts
USERNAME已经不为空。
注意:从ServletContext中删除一个对象的方法:重起JSP/Servlet容器或者使用ServletContext.removeAttribute()方法。
Using Servlets to Retrieve HTTP Data
在这一节,我们将实现servlet如何查找从客户端传送过来的信息。
有三个方法被用来查找:getParameter(), getParameterValues(), 和 getParameterNames()。每个方法的定义如下:
public String ServletRequest.getParameter(String name);
public String[] ServletRequest.getParameterValues(String name);
public Enumeration ServletRequest.getParameterNames ();
getParameter()方法返回单个字符串或者null(如果参数不存在),使用这个方法你确保只返回单个值。如果返回多个值,你必须使用getParameterValues()方法,它将返回一个字符串数组或返回null。getParameterNames()返回请求的参数名称的集合或者空集合。
为了了解如何使用这些方法查找数据,让我们来看servlet的Post方法,它是如何查找参数的,并把取得的值返回到客户端。
列表2.4: ParameterServlet.java.
-----------------------------------------------------------------
package chapter2;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class ParameterServlet extends HttpServlet {
public void init(ServletConfig config)
throws ServletException {
// Always pass the ServletConfig object to the super class
super.init(config);
}
// Process the HTTP GET request
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
// Process the HTTP POST request
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Parameter Servlet</title>");
out.println("</head>");
out.println("<body>");
// Get an enumeration of the parameter names
Enumeration parameters = request.getParameterNames();
String param = null;
// Iterate over the paramater names,
// getting the parameters values
while ( parameters.hasMoreElements() ) {
param = (String)parameters.nextElement();
out.println(param + " : " +
request.getParameter(param) +
"<BR>");
}
out.println("</body></html>");
out.close();
}
}
首先要注意的是servlet通过request的getParameterNames()方法取得所有的参数名。一旦取得参数集合后,它执行while循环来取得参数名和通过getParameter()来取得参数名对应的参数值,并打印。
创建一个HTML页面访问来ParameterServlet,如下:
列表 2.5: Form.html.
-------------------------------------------------------------
<HTML>
<HEAD>
<TITLE>
Parameter Servlet Form
</TITLE>
</HEAD>
<BODY>
<form
action="servlet/chapter2.ParameterServlet"
method=POST>
<table width="400" border="0" cellspacing="0">
<tr>
<td>Name: </td>
<td>
<input type="text"
name="name"
size="20"
maxlength="20">
</td>
<td>SSN:</td>
<td>
<input type="text" name="ssn" size="11" maxlength="11">
</td>
</tr>
<tr>
<td>Age:</td>
<td>
<input type="text" name="age" size="3" maxlength="3">
</td>
<td>email:</td>
<td>
<input type="text"
name="email"
size="30"
maxlength="30">
</td>
</tr>
<tr>
<td> </td>
<td> </td>
<td> </td>
<td>
<input type="submit" name="Submit" value="Submit">
<input type="reset" name="Reset" value="Reset">
</td>
</tr>
</table>
</FORM>
</BODY>
</HTML>
这个HTML文件包含了一个简单的HTML form,它用来递交到ParameterServlet的请求。
编译servlet,复制class文件到:
<CATALINA_HOME>/webapps/ wileyapp/WEB-INF/classes/chapter2目录下
把HTML文件放到:
<CATALINA_HOME>/webapps/wileyapp/ 目录下。
现在打开浏览器,输入如下地址:http://localhost:8080/wileyapp/Form.html
输入数据,点击提交按钮,输出结果。
JavaServer Pages
JavaServer Pages能产生强大的动态HTML页面。JSPs是直接从Java servlets扩展的,这可以让开发人员在JSP中嵌入JAVA逻辑代码。JSP文件必须以后缀.jsp结尾。下面的代码是一个简单的JSP文件,如下:
<HTML>
<BODY>
<% out.println("HELLO JSP READER"); %>
</BODY>
</HTML>
看起来和HTML文件差不多,只是添加了标记来包含.JAVA代码。源代码的文件为hello.jsp,把文件复制到WEB应用中进行部署。当请求一个JSP文件时,JSP引擎会进行处理。当JSP文件第一次被请求时,它被解析成servlet,被编译后驻留于内存。用这个生成的servlet来处理客户端的请求,并返回结果到客户端。对于以后的请求,服务器会检验JSP文件是否被改变。如果没有改变,服务器调用已经生成的servlet对象。如果JSP文件被修改,JSP引擎会重新编译JSP文件以生成新的Servlet。
注意:实质上JSP就是由HTML和JAVA代码组成的。因此它们可获得servlet一样的资源和功能。
JavaServer Page的组成
这节我们讨论JSP的组成,包括:标签,脚本,隐含对象和标准活动。
(directives, scripting, implicit objects, and standardactions.)
JSP标签(Directives)
JSP标签是JSP页面中提供全局信息的元素。标签的一个例子导入JAVA类列表到JSP中。JSP标签的语法如下:
<%@ directive {attribute="value"} %>
page 指令
表示如下:
<%@ page {attribute="value"} %>
表2.2定义了page指令的属性。
Table 2.2 defines the attributes for the page directive.
属性
|
说明
|
language=”scriptingLanguage”
|
告诉服务器什么语言会被编译,目前只支持JAVA
|
extends=”className”
|
当你扩展JSP时,指明JSP的父类。这么做会限制JSP/Servlet的自由度,因此不推荐。
|
import=”importList”
|
定义导入到JSP中的JAVA类包,用分号来分隔不同的类包。
|
session=”true|false”
|
定义是否在JSP中使用session。缺省值为true。
|
buffer=”none|size in kb”
|
是否对输出流进行缓冲。缺省值为8KB.
|
autoFlush=”true|false”
|
定义输出流是否会被自动输出,不管缓冲是否已满。缺省值为true。
|
isThreadSafe=”true|false”
|
告诉JSP引擎这个页面同时可为多个请求服务。缺省值为true。如果设置为false,只能被用于单线程。
|
info=”text”
|
通过调用Servlet.getServletInfo()方法访问JSP页面的表示信息
|
errorPage=”error_url”
|
表示相应地址的JSP页面来处理抛出的JSP异常。
|
isErrorPage=”true|false”
|
表明JSP页面是否有一个错误处理页面,缺省值为false。
|
contentType=”ctinfo”
|
表示发送到客户端的网际协议和字符集。
|
下面的代码片段包含了page指令,导入了java.util包
<%@ page import="java.util.*" %>
include指令
include指令被用来插入文本或者JSP文件。语法如下:
<%@ include file="relativeURLspec" %>
这个file属性通常为HTML文件或者JSP文件。被引用的文件必须为本WEB应用中的。例子如下:
<%@ include file="header.jsp" %>
注意:因为include指令在载入时只转换一次。因此,如果包含的资源改变,这个改变不会体现出来。即如果例子中header.jsp文件改变,当前页(包含header.jsp的页面)显示时并不会改变。
.
taglib指令
taglib指令用来包含自定义标签库,通过URI和关联的前缀来唯一的区分自定义标签。
注意:如果不熟悉JSP自定义标签,你可以学习作者的另一本书“Mastering JSP Custom Tags and Tag Libraries”。
taglib指令显示如下:
<%@ taglib uri="tagLibraryURI" prefix="tagPrefix" %>
taglib在表2.3中描述:
属性
|
定义
|
uri
|
URI是自定义标签的唯一名
|
prefix
|
前缀被用来区分自定义标签的实例
|
下面是taglib的例子:
<%@ taglib uri="http://jakarta.apache.org/taglibs/random-1.0" prefix="rand"%>
JSP脚本
JSP中的脚本是直接绑定到HTML中的JAVA代码片段。
在JSP中有三种脚本语言组件可被调用,每种组件出现于相应的位置。
声明
声明用于定义JAVA变量和方法。JSP声明必须有声明语句。当JSP页第一次被载入时,JSP声明被初始化。初始化后,它们被用于同一JSP页面中的声明,表达式,脚本。语法如下:
<%! declaration %>
一个简单的变量声明:
<%! String name = new String("BOB"); %>
一个简单的方法声明:
<%! public String getName() { return name; } %>
为了更好的理解声明,让我们把声明放到JSP文件中。这个简单的文件代码如下:
<HTML>
<BODY>
<%! String name = new String("BOB"); %>
</BODY>
</HTML>
当文件被载入时,JSP被转换成servlet代码,声明放在servlet的声明区域内。
它在所有的JSP组件中均可使用,相当于类的成员变量。
注意:所有的JSP声明定义为类水平的,因此JSP的所有表达式和脚本中均有效。
表达式
JSP表达式在容器中计算。它在请求时进行计算,计算结果插入到JSP文件的表达式位置上。如果表达式结果不能被转换成字符串,translation-time错误会产生。
表达式语法如下:
<%= expression %>
表达式代码片段:
Hello <B><%= getName() %></B>
JPS文件中的表达式:
<HTML>
<BODY>
<%! public String getName() { return "Bob"; } %>
Hello <B><%= getName() %></B>
</BODY>
</HTML>
脚本
脚本用来把所有的JSP元素放在一起,它们在请求时被执行。他们在所有的JSP组件中使用。脚本语法如下:Scriptlets are the JSP components that bring all the JSP elements together.
<% scriptlet source %>
当JSP脚本代码被转换成servlet代码时,它生成servlet的service()方法。下面的JSP代码片段用来向客户端打印出“Hello Bob”。
<HTML>
<BODY>
<% out.println("Hello Bob"); %>
</BODY>
</HTML>
你会注意到JSP脚本功能非常强大,在JSP中使用脚本来实现你的逻辑会使你的WEB应用很难管理。因为这个原因,导致了我们要创建定义的标签库。
JSP错误处理
象所有的开发方法一样,JSP需要一种比较完善的错误处理机制。JSP体系结构中提供了一种错误处理解决方案,它通过在JSP中专门指明处理错误的JSP文件来实现。
JSP错误出现最多的是运行时错误,它通常由JSP页面或则是被JSP页面调用的一些对象所引起的。请求时错误(Request-time errors)是因为异常抛出的,它可以在被调用的JSP页面中捕获和处理。异常若未被处理会被传送到客户端。
创建 JSP错误页面
下面创建的JSP错误页面只进行很简单的处理:创建简单的JSP页,告诉JSP引擎这是一个错误处理页面,你需要设置属性isErrorPage=true。代码如下:
列表2.6 errorpage.jsp.
----------------------------------------------------------------------------------
<html>
<%@ page isErrorPage="true" %>
Error: <%= exception.getMessage() %> has been reported.
</body>
</html>
第一行JSP代码告诉编译器,这个JSP页面是一个错误处理页面。
代码片段如下:
<%@ page isErrorPage="true" %>
第二段JSP代码使用了隐含异常对象来输出未在JSP页面中被捕获异常消息,这个隐含的异常对象在所有的JSP错误处理页面都可以使用。
使用JSP错误处理页面
为了查看错误处理页面是如何工作的,让我们来创建JSP页面,它包含一个未捕获的异常。JSP页面如下:
列表2.7: testerror.jsp.
------------------------------------------------------------------------------------
<%@ page errorPage="errorpage.jsp" %>
<%
if ( true ) {
// Just throw an exception
throw new Exception("An uncaught Exception");
}
%>
-----------------------------------------------------
注意:第一行代码设置isErrorPage=”errorpage.jsp”,是为了指明如果JSP页面出现异常,将由errorpage.jsp来处理异常。本例中JSP抛出Exception,将由errorpage.jsp来处理。
把testerror.jsp和errorpage.jsp复制到:
<CATALINA_HOME>/webapps/wileyapp/ 目录下, and open the testerror.jsp
在浏览器中打开,你可以看到浏览器中显示了异常。
隐含对象(Implicit Objects)
作为JSP开发人员,你经常会隐含的访问那些在所有的JSP文件中都可以使用的对象。
如果你使用了这些对象,它们会被JSP引擎分析出,并在生成servlet时插入到对应的位置。
Out对象
Out隐含对象来源于java.io.Writer类,它用于发送输出流到客户端。
最通用的时out.println()方法,它来打印文本信息到客户端浏览器。
列表2.8显示了使用out隐含对象的例子:
--------------------------------------------------------------------------
<%@ page errorPage="errorpage.jsp" %>
<html>
<head>
<title>Use Out</title>
</head>
<body>
<%
// Print a simple message using the implicit out object.
out.println("<center><b>Hello Wiley" +
" Reader!</b></center>");
%>
</body>
</html>
--------------------------------------------------------------------
为了执行这个例子,复制文件到
<CATALINA_HOME>/webapps/ wileyapp/ 目录下,在浏览器中输入如下地址:
http://localhost:8080/wileyapp/out.jsp
你会看到:Hello Wiley Reader!
Request对象
这个隐含对象来源于javax.servlet.http.HttpServletRequest接口。它被关联到每一个HTTP请求。常用它来访问请求参数。你可以调用request对象的带参数名称的getParameter()方法,它将返回一个和参数名称匹配的字符串。
Request对象的使用例子如下
列表2.9: request.jsp.
-----------------------------------------------------------------------
<%@ page errorPage="errorpage.jsp" %>
<html>
<head>
<title>UseRequest</title>
</head>
<body>
<%
out.println("<b>Welcome: " +
request.getParameter("user") + "</b>");
%>
</body>
</html>
----------------------------------------------------
JSP通过参数user调用request.getParameter()方法。这个方法寻找参数列表中的键值user来返回数据。在浏览器中输入如下:
可以看到输出:Welcome:Robert。
Response对象
response隐含对象来源于javax.servlet.http.HttpServletResponse。response对象用于把取得的数据返回到客户端。这个隐含对象可以实现HttpServletRequest所有的功能,和你在servlet处理没有什么区别。Response对象经常用于向客户端输出信息。然而JSP API已经提供了一个流对象out来输出信息到客户端。
PageContext对象
PageContext对象提供访问JSP页面的命名空间。它也提供用来访问其他的JSP隐含对象。比较常用的是使用setAttribute() 和getAttribute()方法设置和寻找对象。
Session对象
Session对象来源于javax.servlet.http.HttpSession。它用于存储客户端请求的信息,因此它是有状态交互式的。
Session对象列表如下:
列表2.10: session.jsp.
-------------------------------------------------------------------------------------
<%@ page errorPage="errorpage.jsp" %>
<html>
<head>
<title>Session Example</title>
</head>
<body>
<%
// get a reference to the current count from the session
Integer count = (Integer)session.getAttribute("COUNT");
if ( count == null ) {
// If the count was not found create one
count = new Integer(1);
// and add it to the HttpSession
session.setAttribute("COUNT", count);
}
else {
// Otherwise increment the value
count = new Integer(count.intValue() + 1);
session.setAttribute("COUNT", count);
}
out.println("<b>You have accessed this page: "
+ count + " times.</b>");
%>
</body>
</html>
复制文件到
<CATALINA_HOME>/wileyapp/ 目录,在浏览器中输入地址:
http://localhost:8080/wileyapp/session.jsp
如果你刷新页面访问次数会增加。
Application对象
Application对象来源于javax.servlet.ServletContext,在本章的前面已讨论过ServletContext。Application对象用于访问存储在ServletContext中的全局范围的对象。Application对象的使用方法可以在本章前面部分看到,在次不做叙述。
Config对象
Config对象来源于ServletConfig,它包含了当前JSP/Servlet所在的WEB应用的配置信息。
Page对象
Page对象来源于当前被访问JSP页面的实例化。它实际使用的是JSP转换成的Servlet。
Exception对象
Exception对象用于捕获JSP抛出的异常。它只有在JSP页面属性isErrorPage=true时才可用。
标准Actions
JSP标准Actions是预先定义的标签。这标签很容易用来封装action。
在JSP中有两种类型的标准action。第一种:JavaBean,第二种:由另外的标准action组成。
JavaBeans有三种相应的标签设置:<useBean>, <setProperty>和<getProperty>.。
在定义三个标签后,我们会创建一个例子:
<jsp:useBean>
<jsp:useBean>是 JavaBean的标准行为。它通过ID号和范围来实例化一个JavaBean 。
表2.4对<jsp:useBean>的属性进行说明,表2.5定义了行为的范围。<jsp:useBean>行为非常灵活如果执行到<useBean>,将去寻找是否存在相同ID和scope,如果实例不存在,它会创建一个,把命名空间和ID号关联并存储起来。语法如下:
<jsp:useBean id="name"
scope="page|request|session|application" tpeSpec>
body
</jsp:useBean>
typeSpec ::=class="className" |
class="className" type="typeName" |
type="typeName" class="className" |
beanName="beanName" type="typeName" |
type="typeName" beanName="beanName" |
type="typeName"
表2.4<jsp:useBean>属性:
属性
|
定义
|
id
|
这个键关联指定范围的实例化对象。这个键大小写敏感。这个id属性的键和page.getAttribute方法取得的是一样。
|
Scope
|
对象的生命周期。范围选项page, request, session, and application.在表2.5中定义。
|
表 2.5: <jsp:useBean>的范围值:
值
|
定义
|
page
|
只能在被创建的页面中使用。当前页面完成工作时,引用的对象会被释放掉。
|
request
|
只为同一个请求服务。只在请求中实例化,也包括转向请求。所有引用的对象在请求完成时被释放。
|
session
|
只处理有相同session的请求,引用的对象也是在session中创建的。当session终止时引用对象被释放。
|
Application
|
在相同的WEB应用中被使用。当JSP/Servlet容器关闭,引用对象被释放。
|
<jsp:setProperty>
用于设置bean属性的值。它要设置的属性所在的对象必须已经存在。
<jsp:setProperty>语法如下:
<jsp:setProperty name="beanName" propexpr />
name属性时bean的实例化名称。
property="*" |
property="propertyName" |
property="propertyName" param="parameterName" |
property="propertyName" value="propertyValue"
表2.6<jsp:setProperty>的属性列表:
属性
|
说明
|
name
|
是通过<jsp:useBean>实例化的bean
|
property
|
设置一个属性的值。如果你对propertyName设置“*“,会取出ServletRequest中所有的参数集合。匹配参数名值类型属性名和设置方法类型。并设置每一个匹配的属性的值。如果参数值为空,相关的属性是左未更改的。.
|
param
|
你要设置值的属性名称。
|
value
|
对bean的属性赋值
|
.
<jsp:getProperty>
用来获取实例化的bean的属性值,转换成java.lang.String类型并产生输出。
在使用前bean必须被实例化。语法如下:
<jsp:getProperty name="name" property="propertyName" />
表2.7介绍了<jsp:getProperty>的属性:
属性
|
说明
|
name
|
获取实例化的bean的名称,在<jsp:useBean>中定义
|
Property
|
从实例化的bean中获取值的属性的名称
|
一个JavaBean例子
为了学习如何使用JavaBean,让我们创建一个例子。这个例子实现一个简单的计数器JavaBean。这个计数器有简单的int类型的count属性。它用来统计当前bean属性被访问的次数。另外它也包含了相应的设置获取属性的方法。
表2.11为Counter bean代码:Counter.java.
----------------------------------------------------------------
package chapter2;
public class Counter {
int count = 0;
39
public Counter() {
}
public int getCount() {
count++;
return count;
}
public void setCount(int count) {
this.count = count;
}
}
----------------------------------------------------------
让我们来看访问JavaBean的JSP页面:
列表 2.12 counter.jsp.
-------------------------------------------------------------
<!-- Set the scripting language to java -->
<%@ page language="java" %>
<HTML>
<HEAD>
<TITLE>Bean Example</TITLE>
</HEAD>
<BODY>
<!-- Instantiate the Counter bean with an id of "counter" -->
<jsp:useBean id="counter" scope="session"
class="chapter2.Counter" />
<%
// write the current value of the property count
out.println("Count from scriptlet code : "
+ counter.getCount() + "<BR>");
%>
<!-- Get the the bean’s count property, -->
<!-- using the jsp:getProperty action. -->
Count from jsp:getProperty :
<jsp:getProperty name="counter" property="count" /><BR>
</BODY>
</HTML>
----------------------------------------------------------------------------------------------
Counter.jsp有四个JSP组件。第一个:告诉JSP容器脚本语言为:java
<%@ page language="java" %>
第二步:用<jsp:useBean>实例化一个Counter对象,id为counter,范围为session。
现在我们可以在JSP中引用名称为counter的实例。下面的代码实例化Counter:
<jsp:useBean id="counter" scope="session" class="chapter2.Counter" />
有两个种方法指明如何获取当前bean属性的值。第一种是在JSP脚本中使用一个方法访问bean属性。它是通过访问bean的ID并调用getCount()得到的。脚本代码如下:
<%
// write the current value of the property count
out.println("Count from scriptlet code : "
+ counter.getCount() + "<BR>");
%>
第二种是通过<jsp:getProperty>取得当前bean属性的值。
访问对应的属性,绑定它的属性值并输出到HTML中。
<!-- Get the bean’s count property, -->
<!-- using the jsp:getProperty action. -->
Count from jsp:getProperty :
<jsp:getProperty name="counter" property="count" /><BR>
当你运行Counter.jsp,你会发现下一次的结果值大于前一次的。这是因为每次访问count属性都会调用getCount()方法,所以每次count的值都会增加。
编译Counter.java,把类文件复制到:
<CATALINA_HOME>/wileyapp/WEB-INF/classes/chapter2/目录下,复制Counter.jsp到
<jsp:param>
提供参数和值。
<jsp:include>,
<jsp:forward>, and <jsp:plugin>
<jsp:param name="name" value="value"/>
表2.8说明了<jsp:param>的属性:
属性
|
说明
|
name
|
引用的参数名称
|
value
|
对应参数名称的值
|
--------------------------------------------------------------------------------------------------------------
深入Struts
zhuan zhi:http://www-128.ibm.com/developerworks/cn/java/l-struts1-1/
作为基于MVC模式的Web应用最经典框架,Struts已经正式推出了1.1版本,该版本在以往版本的基础上,提供了许多激动人心的新功能。本文就将带你走进Struts 1.1去深入地了解这些功能。
说明:希望本文的读者能有一定的Struts使用基础。
Model 2
Struts是基于Model 2之上的,而Model 2是经典的MVC(模型-视图-控制器)模型的Web应用变体,这个改变主要是由于网络应用的特性--HTTP协议的无状态性引起的。Model 2的目的和MVC一样,也是利用控制器来分离模型和视图,达到一种层间松散耦合的效果,提高系统灵活性、复用性和可维护性。在多数情况下,你可以将Model 2与MVC等同起来。
下图表示一个基于Java技术的典型网络应用,从中可以看出Model 2中的各个部分是如何对应于Java中各种现有技术的。
在利用Model 2之前,我们是把所有的表示逻辑和业务逻辑都集中在一起(比如大杂烩似的JSP),有时也称这种应用模式为Model 1,Model 1的主要缺点就是紧耦合,复用性差以及维护成本高。
Struts 1.1 和Model 2
既然Struts 1.1是基于Model 2之上,那它的底层机制也就是MVC,下面是Struts 1.1中的MVC实现示意图:
图解说明:其中不同颜色代表MVC的不同部分:红色(控制器)、紫色(模型)和绿色(视图)
首先,控制器(ActionServlet)进行初始化工作,读取配置文件(struts-config.xml),为不同的Struts模块初始化相应的ModuleConfig对象。比如配置文件中的Action映射定义都保存在ActionConfig集合中。相应地有ControlConfig集合、FormBeanConfig集合、ForwardConfig集合和MessageResourcesConfig集合等。
提示:模块是在Struts 1.1中新提出的概念,在稍后的内容中我们将详细介绍,你现在可以简单地把模块看作是一个子系统,它们共同组成整个应用,同时又各自独立。Struts 1.1中所有的处理都是在特定模块环境中进行的。模块的提出主要是为了解决Struts 1.0中单配置文件的问题。
控制器接收HTTP请求,并从ActionConfig中找出对应于该请求的Action子类,如果没有对应的Action,控制器直接将请求转发给JSP或者静态页面。否则控制器将请求分发至具体Action类进行处理。
在控制器调用具体Action的execute方法之前,ActionForm对象将利用HTTP请求中的参数来填充自己(可选步骤,需要在配置文件中指定)。具体的ActionForm对象应该是ActionForm的子类对象,它其实就是一个JavaBean。此外,还可以在ActionForm类中调用validate方法来检查请求参数的合法性,并且可以返回一个包含所有错误信息的ActionErrors对象。如果执行成功,ActionForm自动将这些参数信息以JavaBean(一般称之为form bean)的方式保存在Servlet Context中,这样它们就可以被其它Action对象或者JSP调用。
Struts将这些ActionForm的配置信息都放在FormBeanConfig集合中,通过它们Struts能够知道针对某个客户请求是否需要创建相应的ActionForm实例。
Action很简单,一般只包含一个execute方法,它负责执行相应的业务逻辑,如果需要,它也进行相应的数据检查。执行完成之后,返回一个ActionForward对象,控制器通过该ActionForward对象来进行转发工作。我们主张将获取数据和执行业务逻辑的功能放到具体的JavaBean当中,而Action只负责完成与控制有关的功能。遵循该原则,所以在上图中我将Action对象归为控制器部分。
提示:其实在Struts 1.1中,ActionMapping的作用完全可以由ActionConfig来替代,只不过由于它是公共API的一部分以及兼容性的问题得以保留。ActionMapping通过继承ActionConfig来获得与其一致的功能,你可以等同地看待它们。同理,其它例如ActionForward与ForwardConfig的关系也是如此。
下图给出了客户端从发出请求到获得响应整个过程的图解说明。
下面我们就来详细地讨论一下其中的每个部分,在这之前,先来了解一下模块的概念。
模块
我们知道,在Struts 1.0中,我们只能在web.xml中为ActionServlet指定一个配置文件,这对于我们这些网上的教学例子来说当然没什么问题,但是在实际的应用开发过程中,可能会有些麻烦。因为许多开发人员都可能同时需要修改配置文件,但是配置文件只能同时被一个人修改,这样肯定会造成一定程度上的资源争夺,势必会影响开发效率和引起开发人员的抱怨。
在Struts 1.1中,为了解决这个并行开发的问题,提出了两种解决方案:
- 多个配置文件的支持
- 模块的支持
支持多个配置文件,是指你能够为ActionServlet同时指定多个xml配置文件,文件之间以逗号分隔,比如Struts提供的MailReader演示例子中就采用该种方法。
<!-- Action Servlet Configuration --> <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml, /WEB-INF/struts-config-registration.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> |
这种方法可以很好地解决修改冲突的问题,不同的开发人员可以在不同的配置文件中设置自己的Action、ActionForm等等(当然不是说每个开发人员都需要自己的配置文件,可以按照系统的功能模块进行划分)。但是,这里还是存在一个潜在的问题,就是可能不同的配置文件之间会产生冲突,因为在ActionServlet初始化的时候这几个文件最终还是需要合并到一起的。比如,在struts-config.xml中配置了一个名为success的<forward>,而在struts-config-registration.xml中也配置了一个同样的<forward>,那么执行起来就会产生冲突。
为了彻底解决这种冲突,Struts 1.1中引进了模块(Module)的概念。一个模块就是一个独立的子系统,你可以在其中进行任意所需的配置,同时又不必担心和其它的配置文件产生冲突。因为前面我们讲过,ActionServlet是将不同的模块信息保存在不同的ModuleConfig对象中的。要使用模块的功能,需要进行以下的准备工作:
1、为每个模块准备一个配置文件
2、配置web.xml文件,通知控制器
决定采用多个模块以后,你需要将这些信息告诉控制器,这需要在web.xml文件进行配置。下面是一个典型的多模块配置:
<init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value></init-param><init-param> <param-name>config/customer</param-name> <param-value>/WEB-INF/struts-config-customer.xml</param-value></init-param><init-param> <param-name>config/order</param-name> <param-value>/WEB-INF/struts-config-order.xml</param-value></init-param> |
要配置多个模块,你需要在原有的一个<init-param>(在Struts 1.1中将其对应的模块称为缺省模块)的基础之上,增加模块对应的<init-param>。其中<param-name>表示为config/XXX的形式,其中XXX为对应的模块名,<param-value>中还是指定模块对应的配置文件。上面这个例子说明该应用有三个模块,分别是缺省模块、customer和order,它们分别对应不同的配置文件。
3、准备各个模块所需的ActionForm、Action和JSP等资源
但是要注意的是,模块的出现也同时带来了一个问题,即如何在不同模块间进行转发?有两种方法可以实现模块间的转发,一种就是在<forward>(全局或者本地)中定义,另外一种就是利用org.apache.struts.actions.SwitchAction。
下面就是一个全局的例子:
... <struts-config> ... <global-forwards> <forward name="toModuleB" contextRelative="true" path="/moduleB/index.do" redirect="true"/> ... </global-forwards> ... </struts-config> |
可以看出,只需要在原有的path属性前加上模块名,同时将contextRelative属性置为true即可。此外,你也可以在<action>中定义一个类似的本地<forward>。
<action-mappings> <!-- Action mapping for profile form --> <action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin" validate="true"> <forward name="success" contextRelative="true" path="/moduleA/login.do"/> </action> </action-mappings> |
如果你已经处在其他模块,需要转回到缺省模块,那应该类似下面这样定义,即模块名为空。
<forward name="success" contextRelative="true" path="/login.do"/> |
此外,你也可以使用org.apache.struts.actions.SwitchAction,例如:
... <action-mappings> <action path="/toModule" type="org.apache.struts.actions.SwitchAction"/> ... </action-mappings> ... |
ActionServlet
我们首先来了解MVC中的控制器。在Struts 1.1中缺省采用ActionServlet类来充当控制器。当然如果ActionServlet不能满足你的需求,你也可以通过继承它来实现自己的类。这可以在/WEB-INF/web.xml中来具体指定。
要掌握ActionServlet,就必须了解它所扮演的角色。首先,ActionServlet表示MVC结构中的控制器部分,它需要完成控制器所需的前端控制及转发请求等职责。其次,ActionServlet被实现为一个专门处理HTTP请求的Servlet,它同时具有servlet的特点。在Struts 1.1中它主要完成以下功能:
- 接收客户端请求
- 根据客户端的URI将请求映射到一个相应的Action类
- 从请求中获取数据填充Form Bean(如果需要)
- 调用Action类的execute()方法获取数据或者执行业务逻辑
- 选择正确的视图响应客户
此外,ActionServlet还负责初始化和清除应用配置信息的任务。ActionServlet的初始化工作在init方法中完成,它可以分为两个部分:初始化ActionServlet自身的一些信息以及每个模块的配置信息。前者主要通过initInternal、initOther和initServlet三个方法来完成。
我们可以在/WEB-INF/web.xml中指定具体的控制器以及初始参数,由于版本的变化以及Struts 1.1中模块概念的引进,一些初始参数被废弃或者移入到/WEB-INF/struts-config.xml中定义。下面列出所有被废弃的参数,相应地在web.xml文件中也不鼓励再使用。
- application
- bufferSize
- content
- debug
- factory
- formBean
- forward
- locale
- mapping
- maxFileSize
- multipartClass
- nocache
- null
- tempDir
ActionServlet根据不同的模块来初始化ModuleConfig类,并在其中以XXXconfig集合的方式保存该模块的各种配置信息,比如ActionConfig,FormBeanConfig等。
初始化工作完成之后,ActionServlet准备接收客户请求。针对每个请求,方法process(HttpServletRequest request, HttpServletResponse response)将被调用。该方法指定具体的模块,然后调用该模块的RequestProcessor的process方法。
protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { RequestUtils.selectModule(request, getServletContext()); getRequestProcessor(getModuleConfig(request)).process(request, response);} |
RequestProcessor包含了Struts控制器的所有处理逻辑,它调用不同的processXXX方法来完成不同的处理。下表列出其中几个主要的方法:
方法 | 功能 |
processPath | 获取客户端的请求路径 |
processMapping | 利用路径来获得相应的ActionMapping |
processActionForm | 初始化ActionForm(如果需要)并存入正确的scope中 |
processActionCreate | 初始化Action |
processActionPerform | 调用Action的execute方法 |
processForwardConfig | 处理Action返回的ActionForward |
ActionForm
对于ActionForm你可以从以下几个方面来理解它:
- ActionForm表示HTTP窗体中的数据,可以将其看作是模型和视图的中介,它负责保存视图中的数据供模型或者视图使用。Struts 1.1文档中把它比作HTTP和Action之间的防火墙,这体现了ActionForm具有的过滤保护的作用,只有通过ActionForm验证的数据才能够发送到Action处理。
- ActionForm是与一个或多个ActionConfig关联的JavaBean,在相应的action的execute方法被调用之前,ActionForm会自动利用请求参数来填充自己(初始化属性)。
- ActionForm是一个抽象类,你必须通过继承来实现自己的类。
ActionForm首先利用属性的getter和setter方法来实现初始化,初始化完毕后,ActionForm的validate方法被调用,你可以在其中来检查请求参数的正确性和有效性,并且可以将错误信息以ActionErrors的形式返回到输入窗体。否则,ActionForm将被作为参数传给action的execute方法以供使用。
ActionForm bean的生命周期可以设置为session(缺省)和request,当设置为session时,记得在reset方法中将所有的属性重新设置为初始值。
由于ActionForm对应于HTTP窗体,所以随着页面的增多,你的ActionForm将会急速增加。而且可能同一类型页面字段将会在不同的ActionForm中出现,并且在每个ActionForm中都存在相同的验证代码。为了解决这个问题,你可以为整个应用实现一个ActionForm或者至少一个模块对应于一个ActionForm。
但是,聚合的代价就是复用性很差,而且难维护。针对这个问题,在Struts 1.1中提出了DynaActionForm的概念。
DynaActionForm类
DynaActionForm的目的就是减少ActionForm的数目,利用它你不必创建一个个具体的ActionForm类,而是在配置文件中配置出所需的虚拟ActionForm。例如,在下表中通过指定<form-bean>的type为"org.apache.struts.action.DynaActionForm"来创建一个动态的ActionForm--loginForm。
<form-beans> <form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="actionClass" type="java.lang.String"/> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> </form-beans> |
动态的ActionForm的使用方法跟普通的ActionForm相同,但是要注意一点。普通的ActionForm对象需要为每个属性提供getter和setter方法,以上面的例子而言,我们需要提供getUsername() 和 setUsername()方法取得和设置username属性,同样地有一对方法用于取得和设置password属性和actionClass属性。
如果使用DynaActionForm,它将属性保存在一个HashMap类对象中,同时提供相应的get(name) 和 set(name)方法,其中参数name是要访问的属性名。例如要访问DynaActionForm中username的值,可以采用类似的代码:
String username = (String)form.get("username"); |
由于值存放于一个HashMap对象,所以要记得对get()方法返回的Object对象做强制性类型转换。正是由于这点区别,如果你在Action中非常频繁地使用ActionForm对象,建议还是使用普通的ActionForm对象。
在Struts 1.1中,除了DynaActionForm以外,还提供了表单输入自动验证的功能,在包org.apache.struts.validator中提供了许多有用的类,其中最常见的就是DynaValidatorForm类。
DynaValidatorForm类
DynaValidatorForm是DynaActionForm的子类,它能够提供动态ActionForm和自动表单输入验证的功能。和使用DynaActionForm类似,你必须首先在配置文件中进行配置:
<form-beans> <form-bean name="loginForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="actionClass" type="java.lang.String"/> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean></form-beans> |
同时要定义验证的插件:
<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/> </plug-in> |
其中的validator.xml和validator-rules.xml分别表示验证定义和验证规则的内容(可以合并在一起),比如针对上例中的DynaValidatorForm,我们有如下验证定义(validator.xml):
<?xml version="1.0" encoding="ISO-8859-1" ?><!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN" "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd"><!-- Validation Rules $Id: validation.xml--><form-validation> <!-- ========== Default Language Form Definitions ===================== --><formset> <form name="loginForm"> <field property="username" depends="required, minlength,maxlength"> <arg0 key="prompt.username"/> <arg1 key="${var:minlength}" name="minlength" resource="false"/> <arg2 key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> <var> <var-name>minlength</var-name> <var-value>3</var-value> </var> </field> <field property="password" depends="required, minlength,maxlength" bundle="alternate"> <arg0 key="prompt.password"/> <arg1 key="${var:minlength}" name="minlength" resource="false"/> <arg2 key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> <var> <var-name>minlength</var-name> <var-value>3</var-value> </var> </field> </form> </formset></form-validation> |
从上述定义中,我们可以看到对于字段username有三项验证:required, minlength, maxlength,意思是该字段不能为空,而且长度在3和16之间。而validator-rules.xml文件则可以采用Struts提供的缺省文件。注意在<form-bean>中定义的form是如何与validation.xml中的form关联起来的。最后,要启动自动验证功能,还需要将Action配置的validate属性设置为true。
<action path="/login" type="com.ncu.test.LoginAction"name="loginForm" scope="request" input="tile.userLogin"validate="true"> |
此时,Struts将根据xml配置文件中的定义来检验表单输入,并将不符合要求的错误信息输出到页面。但是你可能会想:这个功能虽然好,可是什么检验都跑到服务器端执行,效率方面和用户易用性方面是不是有些问题?你可能会怀念起那简单的JavaScript客户端验证。
不用担心,在Struts 1.1中也支持JavaScript客户端验证。如果你选择了客户端验证,当某个表单被提交以后,Struts 1.1启动客户端验证,如果浏览器不支持JavaScript验证,则服务器端验证被启动,这种双重验证机制能够最大限度地满足各种开发者的需要。JavaScript验证代码也是在validator-rules.xml文件中定义的。要启动客户端验证,你必须在相应的JSP文件中做如下设置:
- 为<html:form>增加onsubmit属性
- 设置Javascript支持
下表中列出了一JSP文件的示例代码,红字部分为Javascript验证所需代码。
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %><table bgcolor="#9AFF9A" cellspacing="0" cellpadding="10" border="1" width="100%"> <tr> <td> <table cellspacing="0" cellpadding="0" border="0" width="100%"> <tr bgcolor="#696969"> <td align="center"> <font color="#FFFFFF">Panel 3: Profile</font> </td> </tr> <tr> <td><br> <html:errors/> <html:form action="/login.do" focus="username" onsubmit="return validateLoginForm(this);"> <html:hidden property="actionClass"/> <center> <table> <tr> <td>UserName:</td> <td><html:text property="username" size="20"/></td> </tr> <tr> <td>Password:</td> <td><html:password property="password" size="20"/></td> </tr> <tr> <td colspan=2><html:submit property="submitProperty" value="Submit"/></td> </table> </center> </html:form> <html:javascript formName="loginForm" dynamicJavascript="true" staticJavascript="false"/> <script language="Javascript1.1" src="staticJavascript.jsp"></script> </td> </tr> </table> </td> </tr></table> |
其中onsubmit的值为"return validateLoginForm(this);",它的语法为:
return validate + struts-config.xml中定义的form-bean名称 + (this);
staticJavascript.jsp的内容为:
<%@ page language="java" %><%-- set document type to Javascript (addresses a bug in Netscape according to a web resource --%><%@ page contentType="application/x-javascript" %><%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %><html:javascript dynamicJavascript="false" staticJavascript="true"/> |
如果validator-rules.xml中定义的基本验证功能不能满足你的需求,你可以自己添加所需的验证类型。
Action
我们通过继承Action类来实现具体的执行类。具体Action类的功能一般都在execute(以前是perform方法)方法中完成,其中主要涉及到以下几个方面:
- 辅助ActionForm进行一些表单数据的检查。
- 执行必要的业务逻辑,比如存取数据库,调用实体bean等。
- 更新服务器端的bean数据,后续对象中可能会用到这些数据,比如在JSP中利用bean:write来获得这些数据。
- 根据处理结果决定程序的去处,并以ActionForward对象的形式返回给ActionServlet。
提示:由于在Action和ActionForm中都可以实现验证方法,那么如何来安排它们之间的分工呢?一般来说,我们秉着MVC分离的原则,也就是视图级的验证工作放在ActionForm来完成,比如输入不能为空,email格式是否正确,利用ValidatorForm可以很轻松地完成这些工作。而与具体业务相关的验证则放入Action中,这样就可以获得最大ActionForm重用性的可能。
前面我们提到过,我们主张将业务逻辑执行分离到单独的JavaBean中,而Action只负责错误处理和流程控制。而且考虑到重用性的原因,在执行业务逻辑的JavaBean中不要引用任何与Web应用相关的对象,比如HttpServletRequest,HttpServletResponse等对象,而应该将其转化为普通的Java对象。关于这一点,可以参考Petstore中WAF框架的实现思路。
此外,你可能还注意到execute与perform的一个区别:execute方法简单地掷出Exception异常,而perform方法则掷出ServletException和IOException异常。这不是说Struts 1.1在异常处理功能方面弱化了,而是为了配合Struts 1.1中一个很好的功能--宣称式异常处理机制。
宣称式异常处理
和EJB中的宣称式事务处理概念类似,宣称式异常处理其实就是可配置的异常处理,你可以在配置文件中指定由谁来处理Action类中掷出的某种异常。你可以按照以下步骤来完成该功能:
- 实现org.apache.struts.action.ExceptionHandler的子类,覆盖execute方法,在该方法中处理异常并且返回一个ActionForward对象
- 在配置文件中配置异常处理对象,你可以配置一个全局的处理类或者单独为每个Action配置处理类
下表就定义了一个全局的处理类CustomizedExceptionHandler,它被用来处理所有的异常。
<global-exceptions> <exception handler="com.yourcorp.CustomizedExceptionHandler" key="global.error.message" path="/error.jsp" scope="request" type="java.lang.Exception"/></global-exceptions> |
其中具体的参数含义,可以参考ExceptionHandler.java源文件。
taglib
讲完了模型和控制器,接下来我们要涉及的是视图。视图的角色主要是由JSP来完成,从JSP的规范中可以看出,在视图层可以"折腾"的技术不是很多,主要的就是自定义标记库的应用。Struts 1.1在原有的四个标记库的基础上新增了两个标记库--Tiles和Nested。
其中Tiles除了替代Template的基本模板功能外,还增加了布局定义、虚拟页面定义和动态页面生成等功能。Tiles强大的模板功能能够使页面获得最大的重用性和灵活性,此外可以结合Tiles配置文件中的页面定义和Action的转发逻辑,即你可以将一个Action转发到一个在Tiles配置文件中定义的虚拟页面,从而减少页面的数量。比如,下表中的Action定义了一个转发路径,它的终点是tile.userMain,而后者是你在Tiles配置文件中定义的一个页面。
<!-- ========== Action Mapping Definitions ============================== --><action-mappings> <!-- Action mapping for profile form --> <action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin" validate="true"> <forward name="success" path="tile.userMain"/> </action> </action-mappings> |
Tiles配置文件:tiles-defs.xml
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration//EN" "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"><tiles-definitions> <!-- ======================================================= --> <!-- Master definitions --><!-- ======================================================= --> <!-- Page layout used as root for all pages. --> <definition name="rootLayout" path="/tiles-layouts/rootLayout.jsp"> <put name="titleString" value="CHANGE-ME"/> <put name="topMenu" value="/tiles-components/topMenu.jsp"/> <put name="leftMenu" value="/tiles-components/panel1.jsp"/> <put name="body" value="CHANGE-ME"/> <put name="footer" value="/tiles-components/footer.jsp"/> </definition> <!-- ======================================================= --> <!-- Page definitions --> <!-- ======================================================= --> <!-- User Login page --> <definition name="tile.userLogin" extends="rootLayout"> <put name="titleString" value="User Login"/> <put name="body" value="/src/userLogin.jsp"/> </definition> <!-- User Main page --> <definition name="tile.userMain" extends="rootLayout"> <put name="titleString" value="User Main"/> <put name="body" value="/src/userMain.jsp"/> </definition></tiles-definitions> |
而Nested标记库的作用是让以上这些基本标记库能够嵌套使用,发挥更大的作用。
Commons Logging 接口
所谓的Commons Logging接口,是指将日志功能的使用与日志具体实现分开,通过配置文件来指定具体使用的日志实现。这样你就可以在Struts 1.1中通过统一的接口来使用日志功能,而不去管具体是利用的哪种日志实现,有点于类似JDBC的功能。Struts 1.1中支持的日志实现包括:Log4J,JDK Logging API, LogKit,NoOpLog和SimpleLog。
你可以按照如下的方式来使用Commons Logging接口(可以参照Struts源文中的许多类实现):
package com.foo;// ...import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;//... public class Foo { // ... private static Log log = LogFactory.getLog(Foo.class); // ... public void setBar(Bar bar) { if (log.isTraceEnabled()) { log.trace("Setting bar to " + bar); } this.bar = bar; }// ...} |
而开启日志功能最简单的办法就是在WEB-INF/classes目录下添加以下两个文件:
commons-logging.properties文件:
# Note: The Tiles framework now uses the commons-logging package to output different information or debug statements. Please refer to this package documentation to enable it. The simplest way to enable logging is to create two files in WEB-INF/classes:# commons-logging.properties# org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog# simplelog.properties# # Logging detail level,# # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").#org.apache.commons.logging.simplelog.defaultlog=traceorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog |
simplelog.properties文件:
# Logging detail level,# Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").org.apache.commons.logging.simplelog.defaultlog=fatal |
这里我们采用的日志实现是SimpleLog,你可以在simplelog.properties文件指定日志明细的级别:trace,debug,info,warn,error和fatal,从trace到fatal错误级别越来越高,同时输出的日志信息也越来越少。而这些级别是和org.apache.commons.logging.log接口中的方法一一对应的。这些级别是向后包含的,也就是前面的级别包含后面级别的信息。
----------------------------------------------------------------------------------------------
Struts之HTML标签库详解 (中文版)
Struts
提供了五个标签库,即:
HTML
、
Bean
、
Logic
、
Template
和
Nested
。
标签库
|
说明
|
HTML
标签
|
用来创建能够和
Struts
框架和其他相应的
HTML
标签交互的
HTML
输入表单
|
Bean
标签
|
在访问
JavaBeans
及其属性,以及定义一个新的
bean
时使用
|
Logic
标签
|
管理条件产生的输出和对象集产生的循环
|
Template
标签
|
随着
Tiles
框架包的出现,此标记已开始减少使用
|
Nested
标签
|
增强对其他的
Struts
标签的嵌套使用的能力
|
标签的公共特征
使用固定属性名称的
Struts
标签:
属性
|
说明
|
id
|
命名自定义标签创建时的脚本变量名。
|
name
|
指出关键字值,在该关键字下可以找到一个存在的
bean
。如果给出了
scope
属性,则仅仅在
scope
中查找。否则,根据标准的顺序在各种
scope
中查找:
(page, request, session, or application)
。
|
property
|
指出
bean
中的某个属性,可以在其中检索值。如果没有标明,则使用对象本身的值。
|
scope
|
定义了
Bean
在哪个范围
(page, request, session, or application)
中被查找。如果没有标明按顺序查找。脚本变量
(
见
id)
将在相同的范围中创建。
|
Struts
标签也支持嵌套引用,例如:
Property="foo.bar.baz"
这相当于进行下面的调用:
getFoo().getBar().getBaz()
;
或者做为
setter
:
getFoo().getBar().setBaz(value)
;
|
虽然
Struts
标签的设计原意是为了避免使用
scriptlet
,
scriptlet
的表达式还能够提供给所有的
Struts
标签使用。但请确保使用完整的表达式
:
错误:
<html:link href="'<%= "/" + name %>/index.jsp>'>
正确:
<html:link href="'<%= "/" + name + "/index.jsp" %>'> //
表达式必须提供整个属性值
|
Html
标签库
1.
<html>
标签
它有两个属性:
locale
和
xhtml
,两者都不是必需的。
<html:html locale=/"true/">
此行代码解析后:
<html lang=/"en/">
|
2.
说明:生成的结果取决于
Struts
应用程序所位于的服务器的
locale
。如果你将应用程序部署到一个不同
locale
的服务器,你不需要改变代码,
Locale
会自动调整。
3.
<base>
标签:表示所包含页面的绝对位置。这个标签只有内嵌在
head
标签中才有效。
<html:base/>
此行代码解析后:
<base href=/"http://www.mymain.com/myStrutsApp/testing.jsp/">
|
4.
<img>
标签
最重要的属性
page
:图象文件的路径,前面必须带有一个斜线。
其它属性:
heignt
、
width
、
alt
。
<html:img page=/"/logo.gif/" height=/"50/" width=/"200/" alt=/"Web Logo/"/>
|
5.
<link>
标签
<html:link page=/"/index.html/">Click demo</html:link>
此行代码解析后:
<a href=/"/index.html/">Click demo</a>
|
6.
<errors>
标签:通过一个简单的
<html:errors/>
标签,你就可以在一个
JSP
页面上显示完全自定义的错误信息。功能超强大!!
说明:这个标签在
Request
对象的属性集合中查找
reserved key
。如果它找到一个
reserved key
,它就假设这个
key
是一个
String
、或是一个
String
数组
(它包含在模块的
MessageResources
中查找的
message keys
)、或是类型为
org.apache.struts.action.ActionErrors
的一个对象。
如果在应用程序资源中存在相应的信息,那么就可以用下面这些可选的
message keys
:
· errors.header or errors.prefix
:相应的信息在错误信息的单独列表前显示。
· errors.footer or errors.suffix
:相应的信息在错误信息的单独列表后显示。
7.
<form>
标签系列
使用
<form>
标签时必须遵循一些规则:
1.
标签中必须包含一个
action
属性,它是这个标签中唯一必需的属性。如果不具备该属性则
JSP
页面会抛出一个异常。之后你必须给这个
action
属性指定一个有效值。一个有效值是指应用程序的
Struts
配置文件中元素里的任何一个子元素的访问路径。而且相应的元素中必须有一个
name
属性,它的值是
form bean
的名称。
<html:form action=/"/login/" >
如果你有上述一个标签
,那么你的
Struts
配置文件的元素中必须有一个如下显示为粗体的元素:
<action-mappings> <action path=/"/login/" type=/"com.javapro.struts.LoginAction/" name=/"loginForm/" scope=/"request/" input=/"/login.jsp/"> <forward name=/"success/" path=/"/mainMenu.jsp/"/> </action> . . . </action-mappings> //
这就是说一个
form
标签是和
form bean
相关联的。
|
2.
3.
任何包含在
<form>
中用来接收用户输入的标签(
<text>
、
<password>
、
<hidden>
、
<textarea>
、
<radio>
、
<checkbox>
、
<select>
)必须在相关的
form bean
中有一个指定的属性值。比如,如果你有一个属性值被指定为
“username”
的
<text>
标签,那么相关的
form bean
中也必须有一个名为
“username”
的属性。输入
<text>
标签中的值会被用于生成
form bean
的
userName
属性。
<form>
标签还有一些不是必须但很有用的
“
次要
”
属性。
比如,你可以用
focus
属性来生成
JavaScript
,它会
“
定焦
”
(
focus
)到该
form
所包含的一个元素上。使用
focus
属性时你需要给它指定元素的名称。
<body> <html:form action=/"/login/" focus=/"password/"> User Name: <html:text property=/"userName/"/> <br>Password: <html:text property=/"password/"/> <br><html:submit/> </html:form> </body>
代码解析后:
<body> <form name=/"loginForm/" method=/"post/" action=/"/myStrutsApp/login.do/"> User Name: <input type=/"text/" name=/"userName/" value=/"/"> <br>Password: <input type=/"text/" name=/"password/" value=/"/"> <br><input type=/"submit/" value=/"Submit/"> </form> <script language=/"JavaScript/" type=/"text/javascript/"> <!-- if (document.forms[/"loginForm/"].elements[/"password/"].type != /"hidden/") document.forms[/"loginForm/"].elements[/"password/"].focus() // --> </script>
</body>
|
有没有看到这个标签库是如何建立
JavaScript
来定焦到
password
元素上的
?
这也是该库让人着迷的地方之一。你不用担心如何在客户端进行编程,它会帮你自动生成。
还可以看到,
<form>
标签中
method
属性的缺省值是
POST
。
<text>
标签、
<hidden>
标签、
<textarea>
标签、
<radio>
标签、
<checkbox>
标签、
<submit>
标签、
<reset>
标签:
都有一个
property
属性,最后会被转换成
HTML
中的
name
属性,当然还有
name
和
value
属性。
<password>
标签
<html:password property=/"password/" redisplay=/"false/"/>
|
该标签中的一个很重要的属性是
"redisplay"
,它用于重新显示以前输入到这个区域中的值。该属性的缺省值为
true
。然而,为了使
password
不能被重新显示,你或许希望将该属性的值设为
false
。
<select>
标签和
<option>
标签:
<html:select property=/"color/" size=/"3/"> <html:option value=/"r/">red</html:option> <html:option value= /"g/">green</html:option> <html:option value= /"b/">blue</html:option> </html:select>
|
遗补
:
1.)<html:link>
标签
forward
属性:链接到一个
global forward
上;
action
属性:链接到一个
action mapping
上;
href
属性:这个链接会转发给控制器,由控制器做决定;
page
属性:一个相对的链接。
用
page
属性链接到
action
上:
<html:link page="/html-link.do"> Linking with the page attribute. </html:link>
|
注意,上面的代码中你不必指定
web
的关联。相反的,如果你使用
href
属性,你就必须像下面所示指出
web
的关联
(
这里的关联就是
struts-exercise)
:
<html:link href="/struts-exercise-taglib/html-link.do"> Using Href </html:link>
|
很明显,当你在相同的
web
应用程序中做链接是,它比
page
属性更加好。你也能用
href
在不同的服务器上创建链接:
另一种链接到
html-link.do
的方法是用
action
属性:
<html:link action="/html-link"> Using Action attribute </html:link>
|
你也可以以硬编码的方式使用参数:
<html:link page="/htmllink.do?doubleProp=3.3&longProp=32"> Double and long via hard coded changes </html:link>
|
或者使用
paramId, paramName, and paramProperty
属性:
<html:link page="/html-link.do" paramId="booleanProperty" paramName="testbean" paramProperty="nested.booleanProperty"> Boolean via paramId, paramName, and paramValue </html:link>
|
解析后的代码:
<a href="/struts-exercise-taglib/html-link.do?booleanProperty=false"> Boolean via paramId, paramName, and paramValue </a>
|
另外,还能使用带
name
属性的
Map
来实现传递多个参数:
<% java.util.HashMap newValues = new java.util.HashMap(); newValues.put("floatProperty", new Float(444.0)); newValues.put("intProperty", new Integer(555)); newValues.put("stringArray", new String[] { "Value 1", "Value 2", "Value 3" }); pageContext.setAttribute("newValues", newValues); %> ... <html:link action="/html-link" name="newValues"> Float, int, and stringArray via name (Map) </html:link>
|
你也能够链接到
Map
类型的
action
上,上面的代码解析后的结果:
<html:messages property="property2" message="true" id="msg" header="messages.header" footer="messages.footer"> <tr><td><%= pageContext.getAttribute("msg") %></td></tr> </html:messages>
|
2.) select
和
option
标签
<html:select>
的属性:
property
-与
ActionForm
中的某个属性对应;
size
-显示
option
的数目;
multiple
-默认为
fales
,表示不能多选,当设定为
true
时,
property
对应的
ActionForm
的属性必须为数组。
<html:select property="name" size=6 multiple="true">
<html:option>
的属性:
key
、
local
、
bundle
-指定
Resource Bundle
中的内容。
例如
<html:option value="color1">Orange</html:option>
<html:option value="color1" bundle="htmlselect.Colors" key="htmlselect.red"/>
它和配置文件中的
<message-resources>
元素的
key
属性匹配
--> <message-resource parmeter="HtmlSelectColors" key="htmlselect.Colors"/>
<message-resource>
中配置的资源文件为
HtmlSelectColors.properties
,相关内容为
htmlselect.red=RED
<html:options>
标签,提供了一组
<option>
元素,在
<html:select>
元素中可以包含多个
<html:options>
元素。非常灵活,可以取得集合或数组中的值。
例
1 <html:options collection="coll" property="value" labelProperty="label" />
这指在
coll
的集合中存放了
options
,
value
指实际能被提交的值,
label
是显示给用户的值。
例
2 <html:options property="value" labelProperty="label" /> collection
属性不被指定时,将使用表单相关的
form bean
,
form bean
中
value
属性存放
option value
,
label
属性值显示给用户。
例
3 <html:options name="valueBean" property="values" labelName="labelsBean" labelProperty="labels" />
这个意思是
value
值存放在名为
valueBean
的
bean
的
vlaues
属性中,它是一个
collection
;
label
值也是同样的意思。
<html:optionsCollection>
标签,和
<html:options>
的用法很相似。
例如
<html:select property="custId"><html:optionsCollection property="customers" label="name" value="custId" /></html:select>
这个标签和
org.apache.structs.util.LabelValueBean
结合的很好,如果把
label
和
value
都放到这个对象中,可以很简单的这样应用:
<html:select property="custId"><html:optionsCollection property="customers" /></html:select>
所有评论(0)