struts1.1入门(台湾版1-29章)
http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=1958
入门 01 - Struts 在线资源
Struts的在线资源不少,随便Google都会有一堆,但多数为英文,中文资源比较零散,这边收集几个比较完整的Struts资源网站。
Struttin' With Struts:一个很简单的Struts入门英文网站
Struts User Guide:官方网站的英文使用手册
Matrix Translation: Struts User Guide简体中文翻译
Struts专题:developerWorks 中国网站的Struts专题
入门 02 - Struts基本运作流程
在Struts实现MVC/Model 2方式中,担任控制器(Controller)角色的是org.apache.struts.action.ActionServlet,它继承自 javax.servlet.http.HttpServlet,通常会直接使用ActionServlet,在web.xml中配置相关的组态,就如同配置一般的Servlet一样,您也可以配置一些相关的初始参数给ActionServlet使用。
在MVC/Model 2的架构中,理想上客户端不会直接要求资源或指定请求的JSP页面,所有客户的请求都必须发送至控制器,由控制器决定该转发给哪一个物件进行处理、决定该由哪一个视图页面进行显示与回应。
在Struts中,担任控制器的是ActionServlet,所有的请求都发送给ActionServlet,对於ActionServlet 来说,doGet()会将调用doPost(),所以无论请求是GET或POST在行为上都是相同的,ActionServlet藉由组态档struts -config.xml知道如何分配物件来处理请求,这包括了org.apache.struts.action.ActionMapping、 org.apache.struts.action.ActionForm、org.apache.struts.action.Action等物件的分配,struts-config.xml是整个Struts运作的重心。
ActionServlet会将处理的控制权暂时交给Action物件,在Action中,您可以藉由一些资料传输物件(Data Transfer Object)、辅助类别(Helper Class)等来完成业务请求,Action应当传回ActionForward物件给ActionServlet,之後ActionServlet根据 ActionForward来进行forward给视图或include视图(Viewer)的动作,由视图显示处理的结果。
Struts的视图部份可以是静态网页、JSP网页、Struts自订标签,Struts自订标签可以与Struts的一些物件及资源档案合作使用,但也可以使用其它自订标签,像是JSTL、JSF等。
在Struts 1.1中新增org.apache.struts.action.RequestProcessor类别,有关於使用者请求的物件分配等动作已经大部份交由RequestProcessor来处理,下图是ActionServlet接收到请求之後的一些主要动作,在之後的各主题说明中,您可以藉由这张图您将可以了解struts-config.xml的组态设定意义,以及Struts运作的方式:
入门 03 - HelloStruts - 准备目录与档案
在这个主题中,我们将撰写第一个Struts程式,目的很简单,只是模彷jakarta-struts-1.1.zip中的struts-blank应用程式,让使用者的请求重新导向至ActionServlet,然後forward至一个欢迎页面而已。
在这个Struts程式中,主要在学习ActionServlet的配置,包括了在web.xml及struts-config.xml的配置、资源档案的使用及一些目录管理的方式,我们的应用程式主要为以下的结构:
/HelloStruts
index.jsp
/pages
Welcome.jsp
/WEB-INF
web.xml
/conf
struts-config.xml
/lib
*.jar
/tld
*.tld
/classes
/resources
application.properties
专门设定一个tld与conf目录来管理tld档案与struts-config.xml档案并不是必要的,这边这么作的原因只是避免WEB-INF下放置了过多的档案。
在jakarta-struts-1.1.zip解开後,您可以在webapps目录下找到struts-blank.war档案,这个档案包括了一个基本的Struts应用程式,只要将之放在Servlet容器的webapps下并启动容器,struts-blank.war会自动解压缩并完成部署,您可以浏览http://localhost:8080/struts-blank/来看看这个范例将完成什么效果。
将来您可以直接修改这个基本的Struts应用程式来完成您所需要的程式,而这边我们则是模彷这个程式,看看如何自行部署与组态Struts。
在解开後的jakarta-struts-1.1.zip中,lib目录中包括了*.jar与*.tld档案,为了要完成我们的范例,您至少必须复制以下的*.jar档案至您的应用程式WEB-INF/lib目录中:
commons-beanutils.jar
commons-digester.jar
commons-collections.jar
commons-logging.jar
struts.jar
这四个档案彼此之间且有相依性,是完成这个例子所必须的,而为了使用一些Struts自订标签,您要复制以下的档案至WEB-INF/tld目录中:
struts-bean.tld
struts-html.tld
struts-logic.tld
如果您不想这么麻烦,那么就直接将所有的*.jar复制至WEB-INF/lib目录,将所有的*.tldv复制至WEB-INF/tld目录中就是了。
接下来我们准备讯息资源档案,我们将之放置在WEB-INF/classes/resources目录下,档案名称命名为 application.properties,这个档案可以让我们设定一些key-value对应,可以而用Struts自订标签直接取得这些值,以显示在视图页面中,将之设定在一个资源档案中可以方便管理,并可以用之实现国际的讯息内容,我们的application.properties中的内容如下:
# -- welcome --
welcome.title=Hello!Struts!
welcome.message=Welcome to Your First Struts!
如果您想使用中文讯息,您可以编辑application_zh_TW.properties,Struts预设使用伺服器环境上的 locale设定选择讯息档案,如果客户端(可能是浏览器)有提供接收的语言讯息,则使用客户端所指定的语言,您可以在 application_zh_TW.properties当中先撰写好中文讯息,例如:
# -- welcome --
welcome.title=哈罗!Struts!
welcome.message=这是您的第一个Struts!
然後使用JDK中所附的工具程式native2ascii,将中文转换为Unicode,执行以下指令即可:
native2ascii application_zh_TW.properties application_zh_TW.properties
转换後的结果如下,所有的中文都被转换为Unicode编码:
# -- welcome --
welcome.title="u54c8"u56c9"uff01Struts"uff01
welcome.message="u9019"u662f"u60a8"u7684"u7b2c"u4e00"u500bStruts"uff01
到这边我们的范例所需要的目录与档案都准备好了,接下来剩下的是撰写视图页面与组态档案,这在下一个主题中将说明。
入门 04 - HelloStruts - 组态与视图
在MVC/Model 2中,控制器是必要的核心,这在Struts中是由ActionServlet担任,一个应用程式中使用一个ActionServlet物件,我们必须在web.xml中配置它:
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<display-name>Struts Blank Application</display-name>
<!-- Standard Action Servlet Configuration (with debugging) -->
<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/conf/struts-config.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- Standard Action Servlet Mapping -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- The Usual Welcome File List -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
附带一提的是,如果您的Servlet容器支援Servlet 2.4,您可以改以下面的scheme标签来取代dtd:
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
在ActionServlet的设定上,我们给了个config初始参数,用以指定struts-config.xml的位置,在<servlet-mapping>的设定上,我们将所有以*.do结尾的请求全部交给ActionServlet来处理。
为了要使用Struts自订标签,我们也在web.xml中加入以下的设定:
<!-- Struts Tag Library Descriptors -->
<taglib>
<taglib-uri>/tags/struts-bean</taglib-uri>
<taglib-location>/WEB-INF/tld/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/tags/struts-html</taglib-uri>
<taglib-location>/WEB-INF/tld/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/tags/struts-logic</taglib-uri>
<taglib-location>/WEB-INF/tld/struts-logic.tld</taglib-location>
</taglib>
接下来该组态struts-config.xml了,依照web.xml的内容,我们将之设定在WEB-INF/conf目录中:
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>
<global-forwards>
<forward
name="welcome"
path="/Welcome.do"/>
</global-forwards>
<action-mappings>
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/pages/Welcome.jsp"/>
</action-mappings>
<message-resources parameter="resources.application"/>
</struts-config>
<global-forward>提供一个全局可查找的forward对象,在Struts的组态中,我们可以为每一个< action>标签指定其forward对象,如果在<action>中查找不到,就会至<global- forward>中查找,对於一些共同使用的forward对象,您可以集中於<global-forward>中管理。
在<action-mappings>中用於设定请求的URI与Action物件的对应,也就是什么样的路径请求该使用哪一个 Action物件来处理,每一个<action>标签的设定对应於一个ActionMapping物件,path路径设定为Welcome表示Welcome.do的请求将由type所设定的物件来处理,ForwardAction单纯的将请求forward至另一个对象,forward的对象则是parameter所设定的对象,透过ForwardAction的转发,我们可以避免直接请求一个资源的真正位址,而使得所有的请求都可以先经过控制器ActionServlet来进行调度。
<message-resources>用於设定我们的讯息资源档,parameter中的设定表示我们将使用WEB- INF/classes/resources目录中application为开头的资源档案,预设将读取application.properties,根据区域设定的不同,可以读取不同的区域化讯息资源档案。
接下来我们准备index.jsp,如下:
index.jsp
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<logic:redirect forward="welcome"/>
<logic:redirect>标签会查找struts-config.xml中的<global-forward> 标签中是否设定有"welcome"的forward对象,并使用redirect的方式重新导向至指定的路径,我们之前已经在web.xml中设定了 <welcome-file>为index.jsp,所以当有人请求根目录时,将会以index.jsp的内容回应,而根据 index.jsp与<global-forward>的设定,请求将会被重新导向至Welcome.do,而ActionServlet会根据<action-mappings>中的设定,将请求forward至pages/Welcome.jsp,由於使用forward的方式,所以动作是在容器内进行的,浏览器并不知道这件事,因而浏览器的网址列并不会改变,仍然显示Welcome.do。
Welcome.jsp的内容如下:
Welcome.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<H1><bean:message key="welcome.message"/></H1>
</body>
</html:html>
这边使用了<html:html>标签与<bean:message>标签,标签的使用方式是显而易见、不难理解的,其中<bean:message>的key比对来源就是我们的讯息资源档,也就是application.properties,如果您有设定中文讯息档的话就是application_zh.properties,您可以使用http://localhost: 8080/HelloStruts/来浏览,最後网址列会变成http://localhost: 8080/HelloStruts/Welcome.do,而Welcome.jsp所产生的HTML原始码如下:
<html lang="zh">
<head>
<title>哈罗!Struts!</title>
<base href="http://localhost:8080/HelloStruts/pages/Welcome.jsp">
</head>
<body bgcolor="white">
<H1>这是您的第一个Struts!</H1>
</body>
</html>
入门 05 - 简介ActionMapping类别
在Struts中,ActionServlet只是任务的分派者,它依请求分配任务给其它的物件来执行,而分配的依据是请求的URI以及struts-config.xml的<action-mappings>标签所设定的内容。
<action-mappings>用来描述一组ActionMapping物件,当中的每一个<action>标签都对应一个 ActionMapping物件,当客户端发出请求至ActionServlet时,ActionServlet根据其URI及< action>标签设定的path属性查看对应的ActionMapping物件,ActionMapping物件会告诉 ActionServlet该使用哪一个Action物件(在<action>标签中使用type属性设定),而ActionServlet 再将工作交给该Action物件来执行。
举个例子来说,如果<action-mappings>设定如下:
<action-mappings>
<action
path="Login"
type="onlyfun.caterpillar.LoginAction">
....
</action>
<action
path="Logout"
type="onlyfun.caterpillar.LogoutAction">
....
</action>
</action-mappings>
则当请求的URL是http://localhost:8080/HelloStruts/Login.do,ActionServlet会使用 "Login来找出path设定为Login的ActionMapping物件,该ActionMapping物件告知ActionServlet要使用 onlyfun.caterpillar.LoginAction,而後ActionServlet会将呼叫LoginAction的execute() 方法,并将ActionMapping物件当作参数传递给它。
ActionServlet实际上将工作交给Action物件,然而Action物件的运作必须提供一些参数来描述工作的细节,诸如使用哪一个 ActionForm、forward对象的查找、错误发生时的页面对象等等,Struts将这些资讯包装在ActionMapping中,并作为参数传送给Action物件,以使得Action在需要相关的资讯时可以从ActionMapping中取得。
对於没有定义的ActionMapping,如果客户端请求了怎么办?Struts会丢出 404 Invalid path 的讯息,您可以撰写匿名的ActionMapping来提供自己的错误讯息:
<action>
name="error"
unknown="true"
forward="/pages/error.jsp"/>
如果ActionServlet收到的没有定义的Action请求,则会交由匿名的ActionMapping来处理。
在Struts中,ActionMapping继承自ActionConfig,大部份的属性定义实际上都已经在ActionConfig中完成,不过ActionMapping仍旧存在,根据线上API的说明,继承存在的事实基於它仍是现存应用程式所使用的公用类别。
入门 06 - 使用Action类别
在Struts中,ActionServlet担任分配工作的控制器角色,实际上的工作是交给 Action物件来进行,ActionServlet由ActionMapping得知所使用的Action物件,将工作交给它,并在最後由Action 物件得到一个ActionForward物件,ActionServlet使用这个ActionForward来知道下一个forward的对象。
对於Struts,一个ActionMapping只会生成一个Action物件,当请求到达时,会检查所需的Action物件是否存在,如果不存在则生成一个,之後一直使用它,由於Action物件会一直存在,所以使用Action物件必须注意到执行绪安全问题。
我们透过继承Action类别来使用它,在Struts 1.1中,我们会重新定义execute()方法,在Struts 1.0中的perform()方法已经不建议使用;execute()方法有两个接收不同参数的版本:
public ActionForward execute(ActionMapping mapping,
ActionForm form,
ServletRequest request,
ServletResponse response)
throws Exception;
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception;
由於Action通常处理HTTP相关请求,所以我们通常会使用第二个,execute()方法最後要传回ActionForward物件给ActionServlet。
在MVC/Model 2中,理想上所有的请求都经由ActionServlet来协调转发,客户端不会直接指定真正的位址来请求资源,我们下面直接来看个例子,使用Action物件处理这个功能,首先撰写一个Action类别:
GetAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class GetAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String resource = request.getParameter("resource");
if(resource != null && resource.length() != 0) {
request.setAttribute("resource", resource);
return mapping.findForward("resource");
// return new ActionForward("resource");
}
return mapping.findForward("welcome");
}
}
在这个类别中,如果客户端的请求中包括了请求参数resource,则直接返回一个ActionForward,对象为请求参数的内容,原本我们应该是return new ActionForward("resource"),不过为了说明方便,我们将查找struts-config.xml中的forward对象,如果客户端没有包括resource对象,则直接查找"welcome"的forward对象。
这个类别编译过後,应该位於WEB-INF下并包括package阶层,接著我们要在struts-config.xml中加以定义:
struts-config.xml
<struts-config>
<global-forwards>
<forward
name="welcome"
path="/Welcome.do"/>
</global-forwards>
<action-mappings>
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/pages/Welcome.jsp"/>
<action
path="/GetAction"
type="onlyfun.caterpillar.GetAction">
<forward
name="resource"
path="/pages/showres.jsp"/>
</action>
</action-mappings>
<message-resources parameter="resources.application"/>
</struts-config>
在<action-mappings>定义中,如果是/GetAction开头的URI请求,都会交给GetAction物件来处理,其中<forward>标签定义了如果查找"resource"的forward对象,实际该forward的路径, showres.jsp的内容如下,只是简单的显示请求资源的路径而已:
showres.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<H1>请求资源 ${ resource }</H1>
</body>
</html:html>
启动Servlet容器,并使用http://localhost:8080/HelloStruts/GetAction.do?resource=/images/struts.jpg来浏览,您会得到以下的内容:
<html lang="zh">
<head>
<title>哈罗!Struts!</title>
<base href="http://localhost:8080/HelloStruts/pages/showres.jsp">
</head>
<body bgcolor="white">
<H1>请求资源 /images/struts.jpg</H1>
</body>
</html>
Action物件是执行工作的物件,它主要的工作通常有:
*验证使用者的进程状态
*进一步验证表单物件的资讯
*更新应用程式中物件的状态
*处理客户端的请求
*返回ActionForward给ActionServlet
为了要能够重用Action物件,通常建议不要在Action中加入过多的逻辑,普遍认为Action物件也应属於控制器角色,除了以上必要的工作之外,建议将与业务相关的工作交给辅助类别,减少Action物件中的逻辑,以使得Action物件容易理解它的职责。
基本上不只有Action物件应保持职责清晰,就Struts而言,它的工作是辅助建议MVC/Model 2架构,每个相关的组件除了这个目的之外,没有其它的目的,Struts建立起来的架构也鼓励重用,应当将业务相关的逻辑交由其它的物件来处理,而不是交给Struts组件。
入门 07 - 使用ActionForm类别
ActionForm是表单的物件化表示,它本身其实是个JavaBean,除了标准的getter与setter等方法之外,还提供有reset()、validate()等方法供Struts组件呼叫。
当透过发送请求来到ActionServlet後,ActionServlet会从ActionMapping物件中得知所使用的ActionForm物件,这是在struts-config.xml中设定的,如果所需的ActionForm还不存在就生成一个,之後一直使用它, ActionMapping与ActionForm物件会被当作参数传递给Action物件。
在Struts 1.1中,ActionForm生成之後,会执行RequestProcessor的processPopulate()方法,首先它会呼叫 ActionForm的reset()方法,您可以在当中作一些重清ActionForm属性的动作,然而表单的参数值会与ActionForm的 setter进行比对,如果有名称符合的就将表单的参数值设定给对应的属性。
我们透过继承ActionForm类别来使用它,下面是一个简单的例子:
UserForm.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class UserForm extends ActionForm {
protected String name;
protected String password;
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public void reset(ActionMapping mapping, HttpServletRequest req) {
name = null;
password = null;
}
}
ActionForm类别编译後必须放置於WEB-INF中,并包括套件阶层,我们必须在struts-config.xlm中定义Action物件使用哪一个ActionForm物件:
struts-config.xml
<struts-config>
<form-beans>
<form-bean
name="userForm"
type="onlyfun.caterpillar.UserForm"/>
</form-beans>
<global-forwards>
<forward
name="welcome"
path="/Welcome.do"/>
</global-forwards>
<action-mappings>
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/pages/Welcome.jsp"/>
<action
path="/LoginAction"
type="onlyfun.caterpillar.LoginAction"
name="userForm">
<forward
name="greeting"
path="/pages/greeting.jsp"/>
</action>
</action-mappings>
<message-resources parameter="resources.application"/>
</struts-config>
在这个例子中,<form-bean>标签定义了所使用的ActionForm物件及名称,而在<action>标签的设定中,LoginAction指定了userForm作为其所使用的ActionForm,我们的LoginAction类别如下:
LoginAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String name = ((UserForm) form).getName();
String password = ((UserForm) form).getPassword();
if(name.equals("caterpillar") && password.equals("1234")) {
request.setAttribute("valid_user", form);
return mapping.findForward("greeting");
}
return mapping.findForward("welcome");
}
}
ActionForm被作为参数传递至execute()方法,我们必须转换为其原来的形态UserForm,这样才可以取出当中的属性值,在验证使用者的名称与密码无误後,我们查找greeting的ActionForward对象并传回,这会使得ActionServlet将请求 forward至greeting.jsp:
greeting.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<logic:present scope="request" name="valid_user">
<bean:write name="valid_user" property="name"/>您好,这是您的神秘礼物!
</logic:present>
<logic:notPresent scope="request" name="valid_user">
<html:link forward="welcome">请先登入</html:link>
</logic:notPresent>
</body>
</html:html>
<login:present>标签会查看指定的范围中是不是有指定名称的JavaBean物件,如果有的话就使用< bean:write>写出指定的属性,而<login:notPresent>的作用恰恰相反,如果没有指定名称的JavaBean 物件,就给出一个超连结要求先进行登入,<html:link>的forward属性会查找struts-config.xml中的< global-forward>的welcome设定。
如果验证失败,则查找welcome的ActionForward对象并传回,也就是Welcome.jsp,我们用之为登入表单:
Welcome.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<html:form action="/LoginAction" focus="name">
名称:<html:text property="name" size="20"/><br>
密码:<html:password property="password" size="20"/><br>
<html:submit/> <html:reset/>
</html:form>
</body>
</html:html>
<html:form>指定的LoginAction必须在struts-config.xml中有定义,<html: text>与<html:password>会取出对应的ActionForm物件中对应的属性值,如果使用者填写的内容不正确而被送回Welcome.jsp,使用者将可以看到先前所填写的值,如果不想显示先前所填写的值,则将<html:text>与<html: password>的redisplay属性设定为false即可,这几个Struts标签必须与Action物件及ActionForm物件搭配使用。
接下来您可以测试一下程式,连上http://localhost:8080/HelloStruts/,如果您填写了正确的表单栏位并送出,则结果会如下:
<html lang="zh">
<head>
<title>哈罗!Struts!</title>
<base href="http://localhost:8080/HelloStruts/pages/greeting.jsp">
</head>
<body bgcolor="white">
caterpillar您好,这是您的神秘礼物!
</body>
</html>
ActionForm在Struts中是属於视图组件的一部份,它是表单的物件化表单,表单的参数会自动设定给ActionForm,无法对应的就加以忽略,您可以定义validate()方法来验证表单设定的内容,这在下一个主题中将加以说明,在ActionForm中您可以填入表单值,作一些适当的值转换,进行基本的资料验证,ActionForm是表单资料发送至应用程式前的缓冲区,在某些程度上,它是应用程式的防火墙,您可以在 ActionForm中避免不正确或不安全的资料进入应用程式。
综合一下,ActionForm担任以下的几个职责:
*表单栏位的耕耘者
*资料的缓冲区
*资料的验证
*进行属性值的转换
*应用程式防火墙
入门 08 - ActionError与ActionMessage
ActionForm是表单的物件化,有关於表单资料的完整性检查工作该在其中进行,例如使用者是否填写了所有的栏位,ActionForm中所有的属性是否被设定了,您可以重新定义ActionForm的validate()方法来进行这项工作,例如:
UserForm.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class UserForm extends ActionForm {
protected String name;
protected String password;
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public void reset(ActionMapping mapping, HttpServletRequest req) {
name = null;
password = null;
}
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
if(getName() == null || getUsername().length() < 1) {
errors.add("name",new ActionError("error.name.required"));
}
if(getPassword() == null || getPassword().length() < 1) {
errors.add("password",new ActionError("error.password.required"));
}
return errors;
}
}
当使用者发送表单,而表单中有栏位没有填写时,则请求中会包括参数名称,但是值为空字串,如果ActionForm具有某些属性,而表单并没有发送对应的参数,则不会设定ActionForm中对应的属性,这些属性将为null,我们的validate()中主要在检查这两个情况。
validate()方法会传回ActionErrors物件,ActionErrors可以储存ActionError的讯息,每一个 ActionError会查询资源档中的key-value对应,当validate()丢回ActionErrors物件时, ActionServlet就不会继续进行接下来的工作,而是导回structs-config.xml所设定的位置,例如:
struts-config.xml
<global-forwards>
<forward
name="welcome"
path="/Welcome.do"/>
</global-forwards>
<form-beans>
<form-bean
name="userForm"
type="onlyfun.caterpillar.UserForm"/>
</form-beans>
<action-mappings>
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/pages/Welcome.jsp"/>
<action
path="/LoginAction"
type="onlyfun.caterpillar.LoginAction"
name="userForm"
validate="true"
input="/pages/Welcome.jsp">
<forward name="greeting" path="/pages/greeting.jsp"/>
</action>
</action-mappings>
为了要能使用validate()方法,<action>中的validate属性必须设定为true,而input属性也是必要的,当validate()传回ActionErrors时,就会forward至input属性所设定的位置,ActionErrors中的讯息,我们可以使用<html:errors/>标签来显示,待会就会看到。
ActionForm中验证了属性为null及空字串的可能,这是资料完整性的验证,接下来我们要验证资料的正确性,是否符合我们所设定的名称与密码,我们改写前一个主题的LoginAction,看看写法有何不同:
LoginAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.commons.beanutils.*;
public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String name = (String) PropertyUtils.getSimpleProperty(form, "name");
String password = (String) PropertyUtils.getSimpleProperty(form, "password");
if(!(name.equals("caterpillar") && password.equals("1234"))) {
ActionMessages messages = new ActionMessages();
messages.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("message.namepass.notmatched"));
saveMessages(request, messages);
return mapping.findForward("welcome");
}
else {
request.getSession().setAttribute("valid_user", form);
return mapping.findForward("greeting");
}
}
}
在这次的程式中,我们使用了org.apache.commons.beanutils中的PropertyUtils类别来协助我们取 ActionForm中的值,好处是不用理会ActionForm真正的形态,PropertyUtils会自动帮我们判断, getSimpleProperty()传回的是Object,我们将之转换为String。
ActionMessages是Struts 1.1所新增的类别,它变成了ActionErrors的父类别,同样的,ActionMessage也是Struts 1.1新增的类别,它是ActionError的父类别,资料的格式与完整性检查在ActionForm中我们已经验证了,接下来我们在Action中检查是否符合名称与密码,如果不符合就加入相关的讯息。
在Struts 1.1中特意将Message与Error区别,该是认定所谓的Error是使用者的输入在完整性或格式等上有误,而Message是指输入的资料基本上没有错误,但不能符合後续的商务处理。
为了要能够显示错误与讯息,我们必须在application_zh.properties中加入key-value对应,如下:
application_zh.properties
# -- error --
error.name.required=没有输入名称
error.password.required=没有输入密码
#-- message --
message.namepass.notmatched=名称与密码不正确
为了要能使用中文,记得使用native2ascii工具程式进行转换,接下来我们来看看我们的Welcome.jsp如何撰写,要注意的是在<html:errors/>与<htm:messages/>的使用:
Welcome.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<html:errors/>
<html:messages id="messages" message="true">
<bean:write name="messages"/>
</html:messages>
<h3>请登入</h3>
<html:form action="/Login">
名称:<html:text property="name" size="20"/><br>
密码:<html:password property="password" size="20"/><br>
<html:submit/> <html:reset/>
</html:form>
</body>
</html:html>
如果由於ActionForm传回ActionErrors物件而返回Welcome.jsp,则<html:errors/>标签会显示ActionErrors中的相关错误讯息,我们利用<html:messages/>来检查返回中是否也包括 ActionMessages物件,如果有的话就取出并使用<bean:write/>标签显示之。
下面是执行时未填写栏位所显示的错误讯息的一个例子:
<html lang="zh">
<head>
<title>哈罗!Struts!</title>
<base href="http://localhost:8080/HelloStruts/pages/Welcome.jsp">
</head>
<body bgcolor="white">
<UL>
<LI>没有输入名称
</LI><LI>没有输入密码
</LI></UL>
<h3>请登入</h3>
<form name="UserForm" method="post" action="/HelloStruts/Login.do">
名称:<input type="text" name="name" size="20" value=""><br>
密码:<input type="password" name="password" size="20" value=""><br>
<input type="submit" value="Submit"> <input type="reset" value="Reset">
</form>
</body>
</html>
注意到ActionErrors在Struts 1.2之後可能会被标示为deprecated,将来可能会改以ActionMessages取代,所以<html:errors/>在将来必须以下面的方式来取代:
<html:messages id="msg" >
<bean:write name="msg"/>
</html:messages>
在之前的例子中,在<html:messages/>的属性上设定message为true,这表示显示ActionMessages的内容:
代码:
<html:messages id="messages" message="true">
<bean:write name="messages"/>
</html:messages>
入门 09 - 使用DynaActionForm类别
一个网站中会有许多的表单以供使用者填写资料,越大型的网站越需要更多的ActionForm物件来应付表单的取值,对於一些表单来说,ActionForm只是单纯的设值、取值行为,如果为每一个表单撰写一个ActionForm,那么会是一件单纯重复且无趣的行为,而且在维护上会更加困难。
对於一些单纯的表单来说,您可以使用DynaActionForm类别,您只要在struct-config.xml中设定好相关的设定,就可以自动生成ActionForm物件,这么一来您可以集中在struct-config.xml中管理表单物件,也可以省去不是编码与编译的工夫。
下面是使用DynaActionForm的一个例子:
struts-config.xml
<struts-config>
<form-beans>
<form-bean
name="userForm"
type="org.apache.struts.action.DynaActionForm">
<form-property
name="name"
type="java.lang.String"
initial="nobody"/>
<form-property
name="password"
type="java.lang.String"
initial="nopass"/>
</form-bean>
</form-beans>
<global-forwards>
<forward
name="welcome"
path="/Welcome.do"/>
</global-forwards>
<action-mappings>
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/pages/Welcome.jsp"/>
<action
path="/LoginAction"
type="onlyfun.caterpillar.LoginAction"
name="userForm">
<forward
name="greeting"
path="/pages/greeting.jsp"/>
</action>
</action-mappings>
<message-resources parameter="resources.application"/>
</struts-config>
注意在<form-bean>的type属性,我们设定使用 org.apache.struts.action.DynaActionForm,而Form的属性则使用<form-property> 标签来设定,DymaActionForm的type属性希望得到的一个Java类别名称,所以对於基本资料型态如int,必须以 java.lang.Integer作为Wrapper类别包装。
ActionForm类别的reset()方法预设什么事都不作,除非您重新定义它,DynaActionForm则预设会将所有的资料重设为Java型态的预设值,例如数字重设为0,而Object则重设为null,您也可以使用initial属性来设定重设的值。
DynaActionForm同样也被当作参数传递给execute()方法,DynaActionForm使用Map型态的方法来取值,下面的Action类别示范了如何取得DynaActionForm的属性值:
LoginAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
DynaActionForm dynaForm = (DynaActionForm) form;
String name = (String) dynaForm.get("name");
String password = (String) dynaForm.get("password");
if(name.equals("caterpillar") && password.equals("1234")) {
request.setAttribute("valid_user", form);
return mapping.findForward("greeting");
}
return mapping.findForward("welcome");
}
}
在视图的取值上,DynaActionForm也可以使用<bean:write>来写出其指定的属性值,如果您要使用EL来取得属性值,则必须先取得map物件再指定属性名称,例如 ${valid_user.map.name} ,下面是gretting.jsp的内容示范:
greeting.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<logic:present scope="request" name="valid_user">
<bean:write name="valid_user" property="name"/>您好,这是您的神秘礼物!
${ valid_user.map.name }, ${ valid_user.map.password}
</logic:present>
<logic:notPresent scope="request" name="valid_user">
<html:link forward="welcome">请先登入</html:link>
</logic:notPresent>
</body>
</html:html>
Welcome.jsp的内容与之前的主题相同。
DynaActionForm预设并没有为validate()方法提供任何行为,如果需要的话,您可以透过继承DynaActionForm来定义validate()方法,并於<form-bean>的type中设定为您所撰写的类别。
使用DynaActionForm相当方便,但也必须付出代价,不可避免的,您的strut-config.xml内容将变得冗长,而另一方面, DynaActionForm使用Map来查找属性,所付出的是效能的降低,如果您的ActionForm经常使用,建议还是使用继承 ActionForm的方法来使用表单物件。:
入门 10 - Map-backed ActionForm
有的时候您的表单内容可能是由程式动态产生的,这时候表单上的栏位与参数我们事先并无法得知,因而无法事先决定好ActionForm的属性,在Struts 1.1中,您可以使用Map-backed ActionForm,这样的ActionForm使用Map来储存表单的栏位值,一个例子如下:
UserForm.java
package onlyfun.caterpillar;
import java.util.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class UserForm extends ActionForm {
protected Map map = new HashMap();
public void setValue(String key, Object value) {
map.put(key, value);
}
public Object getValue(String key) {
return map.get(key);
}
public Map getMap() {
return map;
}
public void reset(ActionMapping mapping, HttpServletRequest req) {
map.clear();
}
}
在Action类别中,您可以如下面的方式来取值:
LoginAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
UserForm userForm = (UserForm) form;
String name = (String) userForm.getValue("name");
String password = (String) userForm.getValue("password");
if(name.equals("caterpillar") && password.equals("1234")) {
request.setAttribute("valid_user", form);
return mapping.findForward("greeting");
}
return mapping.findForward("welcome");
}
}
为了要能够设定值给这个ActionForm,在表单上,您要使用name="value(key)"来设定栏位名称,例如:
<html:form action="/LoginAction" focus="name">
名称:<html:text property="value(name)" size="20"/><br>
密码:<html:password property="value(password)" size="20"/><br>
<html:submit/> <html:reset/>
</html:form>
同样的,如果要使用<bean:write>标签来写出ActionForm的值,我们也要使用value(key)的方法来取值,例如:
<logic:present scope="request" name="valid_user">
<bean:write name="valid_user" property="value(name)"/>您好,这是您的神秘礼物!
</logic:present>
在我们的ActionForm中,我们提供了getMap()方法,这样作可以方便透过EL来取值,例如:
${valid_user.map.name}
${valid_user.map.password}
入门 11 - 协同开发 - 模组化程式
Struts中关於ActionForm、Action、ActionMapping等等都是在 struts-config.xml中加以设定,在大型网站的开发中,有很多小团队会负责不同的模组,如果每一个团队都要对struts- config.xml进行设定,将会导致struts-config.xml的版本控制问题。
在Struts 1.1中,您可以为不同的模组分配不同的struts-config.xml设定档,方法是在ActionServlet的config参数後加上後缀字,例如我们将使用者登入的工作切分为login模组,则我们可以这么在web.xml中设定:
web.xml
<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/conf/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>config/login</param-name>
<param-value>/WEB-INF/conf/struts-config-login.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
config/login指定了login模组所使用的设定档struts-config-login.xml,现在login模组的开发人员只要专心於自己的开发与设定档,就如同之前的主题一般的设定,当ActionServlet收到请求时,它是根据模组前缀来了解该使用哪一个模组,例如:
http://localhost:8080/HelloStruts/login/Login.do
像上面的URL,/HelloStruts/login/Login.do是相对於domain,/login/Login.do是相对於 Context,而Login.do是相对於模组,当ActionServlet接收到上面这样的绝对路径,它判断URI中相对於Context的前缀为 login,於是得知该使用login模组,在每个模组的struts-config-xxx.xml中的设定则是相对於模组路径的,也就是说如果是以下的设定:
struts-config-login.xml.java
<action
path="/LoginAction"
type="onlyfun.caterpillar.LoginAction"
name="userForm">
<forward
name="greeting"
path="/greeting.jsp"/>
则LoginAction会自动被加上login前缀,也就是要使用以下的路径才可以正确的请求:
http://localhost:8080/HelloStruts/login/LoginAction.do
为了更清楚的了解,我们实际来看一个例子,首先我们先完成struts-config.xml:
struts-config.xml
<struts-config>
<global-forwards>
<forward
name="welcome"
path="/Welcome.do"/>
</global-forwards>
<global-forwards>
<forward
name="login"
contextRelative="true"
path="/login/Login.do"
redirect="true"/>
</global-forwards>
<action-mappings>
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/pages/Welcome.jsp"/>
</action-mappings>
<message-resources parameter="resources.application"/>
</struts-config>
在<global-forward>的login forward,其路径指向login模组,这个路径是相对於Context的,当ActionServlet进行重新导向时,可以由路径得知该使用login模组。
Welcome.jsp位於/pages目录下,内容如下:
Welcome.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
欢迎!<html:link forward="login">请先登入</html:link>
</body>
</html:html>
在<html:link>的设定中,将会查找struts-config.xml的login forward,执行结果将会是:
<html lang="zh">
<head>
<title>哈罗!Struts!</title>
<base href="http://localhost:8080/HelloStruts/pages/Welcome.jsp">
</head>
<body bgcolor="white">
欢迎!<a href="/HelloStruts/login/Login.do">请先登入</a>
</body>
</html>
接下来看看struts-config-login.xml的设定:
struts-config-login.xml
<struts-config>
<form-beans>
<form-bean
name="userForm"
type="onlyfun.caterpillar.UserForm"/>
</form-beans>
<global-forwards>
<forward
name="login"
path="/Login.do"/>
</global-forwards>
<action-mappings>
<action
path="/Login"
type="org.apache.struts.actions.ForwardAction"
parameter="/login/login.jsp"/>
<action
path="/LoginAction"
type="onlyfun.caterpillar.LoginAction"
name="userForm">
<forward
name="greeting"
path="/greeting.jsp"/>
</action>
</action-mappings>
<message-resources parameter="resources.application"/>
</struts-config>
ForwardAction的parameter接收相对於Context的路径设定,除此之外,所有的设定都是相对於login模组的,为了要请求每一个设定的Action,您都必须加上模组前缀,其中我们的login.jsp位於/login目录下:
login.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<html:form action="/LoginAction" focus="name">
名称:<html:text property="name" size="20"/><br>
密码:<html:password property="password" size="20"/><br>
<html:submit/> <html:reset/>
</html:form>
</body>
</html:html>
这个页面执行之後,有关於Action的部份将会自动被加上模组前缀,执行结果如下,请注意红字标示部份:
<html lang="zh">
<head>
<title>哈罗!Struts!</title>
<base href="http://localhost:8080/HelloStruts/login/login.jsp">
</head>
<body bgcolor="white">
<form name="userForm" method="post" action="/HelloStruts/login/LoginAction.do">
名称:<input type="text" name="name" size="20" value=""><br>
密码:<input type="password" name="password" size="20" value=""><br>
<input type="submit" value="Submit"> <input type="reset" value="Reset">
</form>
<script type="text/javascript" language="JavaScript">
<!--
var focusControl = document.forms["userForm"].elements["name"];
if (focusControl.type != "hidden") {
focusControl.focus();
}
// -->
</script>
</body>
</html>
我们的greeting.jsp位於/login目录下:
greeting.jsp
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<logic:present scope="request" name="valid_user">
<bean:write name="valid_user" property="name"/>您好,这是您的神秘礼物!
</logic:present>
<logic:notPresent scope="request" name="valid_user">
<html:link forward="welcome">请先登入</html:link>
</logic:notPresent>
</body>
</html:html>
到这边关於模组化程式的struts-config-xxx.xml设定、路径与目录设定就说明完毕了,剩下的是ActionForm与Action类别的撰写,这没什么,为了让您能顺利完成范例并进行测试,我们列出如下而不加以说明了:
UserForm.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class UserForm extends ActionForm {
protected String name;
protected String password;
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public void reset(ActionMapping mapping, HttpServletRequest req) {
name = null;
password = null;
}
}
LoginAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String name = ((UserForm) form).getName();
String password = ((UserForm) form).getPassword();
if(name.equals("caterpillar") && password.equals("1234")) {
request.setAttribute("valid_user", form);
return mapping.findForward("greeting");
}
return mapping.findForward("login");
}
}
入门 12 - Struts例外处理
Struts基於JSP/Servlet技术,自然的,您可以在web.xml定义相关的例外处理,关於这部份,请参考这篇文章:
http://www.caterpillar.onlyfun.net/phpBB2/viewtopic.php?t=1136
Struts框架提供每一个Action物件处理自己例外的方法,只要使用<exception>标签在struts-config.xml中定义,例如:
struts-config.xml
<action
path="/LoginAction"
type="onlyfun.caterpillar.LoginAction"
name="userForm"
input="/login.jsp">
<exception
key="login.failure"
path="failure.jsp"
type="onlyfun.caterpillar.LoginFailureException"/>
<forward
name="greeting"
path="/greeting.jsp"/>
key属性的设定是用来匹配讯息资源档中的value值,如果在Action物件丢出type所指定的例外,将转送至path属性所指定的页面,与例外相关的讯息会被封装在ActionError物件中,您可以在failure.jsp中使用<html:error/>来显示相关的错误讯息。
除了为每一个Action物件指定例外的处理,您也可以定义一个全局可匹配的例外,使用<global-exceptions>标签来进行声明:
struts-config.xml
<global-exceptions>
<exception
key="expired.password"
type="app.ExpiredPasswordException"
path="/changePassword.jsp"/>
</global-exceptions>
当例外被丢出时,Struts预设的例外处理类别是org.apache.struts.action.ExceptionHandler,在它的 execute()方法中,会建立一个ActionError包装讯息并加以储存,execute()方法会传回一个ActionForward物件给 ActionServlet以进行forward:
protected ActionForward execute(Exception ex,
ExceptionConfig config,
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws ServletException {
....
}
如果您想要自行处理被丢出的例外,您可以继承ExceptionHandler并重新定义execute()方法,并在<exception>标签中使用属性handler指定您自订的ExceptionHandler类别:
struts-config.xml
<global-exceptions>
<exception
key="expired.password"
type="app.ExpiredPasswordException"
handler="onlyfun.caterpillar.CustomExceptionHandler"
path="/changePassword.jsp"/>
</global-exceptions>
如果您想要深入了解Struts的例外处理方式,可以看看RequestProcessor原始码中的processException()方法,以及ExceptionHandler的execute()方法。
当Action物件丢出例外时,RequestProcessor的processException()方法会看看是否有在< exception>标签中声明,这个工作是委由ActionMapping物件的findException()方法来执行,如果找不到相对应的例外处理,除了标签中声明,这个工作是委由ActionMapping物件的findException()方法来执行,如果找不到相对应的例外处理,除了IOException直接丢出之外,其它的例外则将之包装为ServletException例外再丢出:
ExceptionConfig config = mapping.findException(exception.getClass());
if(config == null) {
....
if(exception instanceof IOException) {
throws (IOException) exception;
}
else if(exception instanceof ServletException) {
throws (ServletException) exception;
}
else {
throws new ServletException(exception);
}
}
如果RequestProcessor的processException()找到对应的例外处理,则载入ExceptionHandler进行处理,最後返回一个ActionForward物件:
Class handlerClass = Class.forName(config.getHandler());
ExceptionHandler handler = (ExceptionHandler) handlerClass.newInstance();
return (handler.execute(exception, config, mapping, form, request, response));
入门 13 - 从ActionServlet到RequestProcessor
在Struts中,担任MVC/Model 2控制器角色核心的是ActionServlet,所有的请求都必须先通过它,在Struts 1.1中,有关於请求的处理大部份已交由RequestProcessor,当ActionServlet收到GET或POST的请求,其doGet() 或doPost()会呼叫process()方法来处理请求:
protected void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
RequestUtils.selectApplication(request, getServletContext());
getApplicationConfig(request).getProcessor().process(request, response);
}
RequestUtils是个工具类,ActionServlet呼叫其selectApplication()方法,藉由 request.getServletPath()来取得请求路径以选择应用程式模块来处理请求,之後从ApplicationConfig物件取得 RequestProcessor物件,将使用者的请求委托它来进行处理。
通常我们将ActionServlet当作黑盒子,我们只是使用它,然而您也可以继承ActionServlet来定义自己的控制器,但由於在 Struts 1.1中大部份的请求已经委托RequestProcessor来处理,继承ActionServlet来定义自己的控制器请求处理意义已经不大,通常的目的是重新定义ActionServlet的init()方法,增加自己的初始化动作:
public class CustomActionServlet extends ActionServlet {
public void init() throws ServletException {
super.init();
// 增加自己的初始化动作
....
}
}
预设的RequestProcessor物件是org.apache.struts.action.RequestProcessor,您可以藉由观看process()方法的原始码来了解它作了哪些事情:
RequestProcessor.java
public void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// 处理 contentType 为 multipart/form-data 的 POST 请求
request = processMultipart(request);
// 取得 URI 路径
String path = processPath(request, response);
if(path == null)
return;
.....
// 确定客户端的位置,是否要将一个Locale物件储存在 session 中
// 配合 <controller> 的 locale 属性使用
processLocale(request, response);
// 确定contentType,预设是 text/html
processContent(request, response);
// 判断<controller>属性nocache是否被设定
// 若是,在 response 中加入防止快取的header
processNoCache(request, response);
// 前置处理,预设返回 true,子类可以重新定义它以决定要不要继续处理
if(!processPreProcess(request, response)) {
return;
}
// 从URI路径确定ActionMapping
ActionMapping mapping = processMapping(request, response, path);
if(mapping == null) {
return;
}
....
// 处理ActionForm,如果没有就新增一个,之後一直使用它
ActionForm form = processActionForm(request, response, mapping);
// 将表单的栏位值填入ActionForm
processPopulate(request, response, form, mapping);
// 判断是否执行ActionForm的validate()方法
if(!processValidate(request, response, form, mapping)) {
return;
}
// 判断 <action> 标签的 forward 或 include 标签是不是被设定
// 这两个标签在设定一个路径,其与 type 属性是互斥的,当设定
// 其中一个属性时,调用 RequestDispatcher 的 forward() 或
// include() ,其作用与设定ForwardAction或IncludeAction相同
// 直接绕送而不再使用Action物件进行接下来的处理
if(!processForward(request, response, mapping)) {
return;
}
if(processInclude(request, response, mapping)) {
return;
}
// 处理Action,如果没有就生成一个,之後一直使用它
Action action = processActionCreate(request, response, mapping);
if(action == null) {
return;
}
// 呼叫Action的execute()或perform()方法,并返回ActionForward
ActionForward forward = processActionPerform(request, response, action, for, mapping);
// 处理ActionForward
processActionForward(request, response, forward);
}
您可以继承RequestProcessor,并改写其中的processXXXXX()方法来自定义请求的处理方式,如果您要使用自己的 RequestProcessor,要要在struts-config.xml中使用<controller>标签来定义,例如:
struts-config.xml
<controller
contentType="text/html;charset=Big5"
locale="true"
nocache="true"
processorClass="onlyfun.caterpillar.CustomRequestProcessor"/>
在Struts 1.1中,新增了<controller>标签,它用於指定给ActionServlet的一些参数,在Struts 1.1之前,这些参数是在<init-params>中加以指定,使用<controller>标签,应用程式中不同的模组也可以指定各自的参数给ActionServlet。
入门 14 - ForwardAction,IncludeAction,SwitchAction
有的时候您只是想要从一个页面或资源转换到另一个资源,在MVC/Model 2的架构中,直接使用页面或资源的路径来叫用并不是一个好的主意,这会使得控制器没有机会处理相关的请求事宜。
您仍应该透过控制器,使用ForwardAction可以帮您完成这个事情,当控制器使用ForwardAction时perform()方法时,它会使用属性parameter所设定的路径进行forward的动作,一个设定ForwardAction的例子如下:
struts-config.xml
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/pages/Welcome.jsp"/>
同样的,有时候您会需要引入一个页面或资源,这时您可以使用IncludeAction,其设定方式与ForwardAction相同,只不过它是用include的方式来调用页面或资源:
struts-config.xml
<action
path="/SomeResource"
type="org.apache.struts.actions.IncludeAction"
parameter="/path/someServlet"/>
SwitchAction用於从一个模组转换至另一个模组,当您的应用程式分作多个模组时,您有两个方法可以在模组之间切换,第一个方法是使用相对於Context的路径来进行 forward 查找,我们之前在介绍模组化时就是使用这个方法:
struts-config.xml
<global-forwards>
<forward
name="login"
contextRelative="true"
path="/login/Login.do"
redirect="true"/>
</global-forwards>
这是在全区可查找的 forward 中的设定,在<action>标签中也可以像上面一样使用<forward>标签,,例如:
struts-config.xml
<action ... >
<forward
name="login"
contextRelative="true"
path="/login/Login.do"
redirect="true"/>
</action>
另一切换模组的方法就是使用SwitchAction,它需要在请求中带两个参数,一个是prefix,用来指定模组前缀名称,一个是page,用来指定相对於模组的资源路径,例如我们可以这么设定:
struts-config.xml
<action-mappings>
<action
path="/LoginSwitch"
type="org.apache.struts.actions.SwitchAction"/>
</action-mappings>
之後我们可以使用这样的路径与参数来请求login模组的Login.do:
http://localhost:8080/HelloStruts/LoginSwitch.do?prefix=/login&page=/Login.do
入门 15 - 使用DispatchAction类别
在之前的主题中,您为每一个动作撰写一个Action类,随著网站的增大,维护Action的成本也就随著增大,您可以使用模组化来管理,而另一方面,在检视一些Action时,您会发现某些Action所执行的,其实是完成一个业务逻辑所需要的连续动作或相关动作,例如帐号的登入、登出等动作。
在Struts中,您可以使用org.apache.struts.actions.DispatchAction类别将完成一个业务逻辑所需要的连续动作或相关动作集中於一个Action类中,在继承DispatchAction後,您不再是重新定义execute()方法,而是撰写自己的动作,execute()方法是在DispatchAction抽象类定义的。
例如我们可以继承DispatchAction来定义一个AccountAction,在当中集中管理一些与帐号相关的操作,一个例子如下:
AccountAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.actions.*;
public class AccountAction extends DispatchAction {
public ActionForward login(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
// 进行一些Login的逻辑
......
}
public ActionForward logout(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
// 进行一些Logout的逻辑
......
}
public ActionForward method1(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
// 进行一些method1的逻辑
......
}
.....
}
我们不再重新定义execute()方法,而是定义我们自己的login()、logout()等方法,这些方法接收与execute()相同的参数,并且也传回ActionForward物件。
使用DispatchAction时,我们要在struts-config.xml定义:
struts-config.xml
<action
path="/account"
type="onlyfun.caterpillar.AccountAction"
parameter="method"
name="userForm">
<forward
name="greeting"
path="/login/greeting.jsp"/>
</action>
主要就是在parameter的属性上,我们指定依method请求参数来指定我们所要使用的方法,例如下面的网址将会执行AccountAction的login()方法:
http://localhost:8080/HelloStruts/account.do?method=login&name=caterpillar&password=1234
注意在请求参数中,我们包括了method=login来指定执行login()方法,同样的,如果您要执行logout()方法,则如下:
http://localhost:8080/HelloStruts/account.do?method=logout
入门 16 - 使用LookupDispatchAction类别
org.apache.struts.actions.LookupDispatchAction类别是DispatchAction类别的子类,与DispatchAction类似的是,它透过请求上的参数来决定该执行哪一个方法,不过LookupDispatchAction多了查询讯息资源档案的功能,LookupDispatchAction的用处之後,就是当一个表单中包括两个以上同名的送出按钮时,可以透过查询讯息资源档来确定相对应的动作。
直接以实例来说明,在继承LookupDispatchAction之後,您要重新定义getKeyMethodMap()方法,并定义好自己的相关处理方法,例如:
ShoppingAction.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.actions.*;
public class ShoppingAction extends LookupDispatchAction {
protected Map getKeyMethodMap() {
Map map = new HashMap();
map.put("button.continue", "continue");
map.put("button.checkout", "checkout");
return map;
}
public ActionForward continue(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
// ......
}
public ActionForward checkout(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
// ......
}
}
假设讯息资源档中包括以下的讯息:
application.properties
button.continue=Continue
button.checkout=Checkout
为了要使用LookupDispatchAction,我们如同DispatchAction一样在struts-config.xml中定义请求参数中该有的名称:
struts-config.xml
<action path="/shopping"
type="onlyfun.caterpillar.ShoppingAction"
parameter="method"
name="cartForm"/>
现在假设您的表单页面包括以下的内容:
<html:form action="/shopping">
<html:submit property="method">
<bean:message key="button.continue"/>
</html:submit>
<html:submit property="method">
<bean:message key="button.checkout"/>
</html:submit>
</html:form>
这些Struts自订标签在执行後会产生以下的内容:
<form name="cartForm" method="post" action="/HelloStruts/shopping.do">
<input type="submit" name="method" value="Continue"/>
<input type="submit" name="method" value="Checkout"/>
</form>
所以当您按下任一个按钮时,请求参数中会包括method=Continue或是method=Checkout,假设是method=Continue好了,LookupDispatchAction会根据它作为value,在讯息资讯档找到对应的key,然後根据key与getKeyMethodMap()得知要执行的方法为continue()方法。
入门 17 - Struts国际化支援
Struts从一开始就支援国际化(internationallization, i18n),我们可以使用讯息资源档案来管理国际化的讯息,就如之前所提过的,您在struts-config.xml中设定讯息资源档的名称与位置:
struts-config.xml
<message-resources parameter="resources.application"/>
这样的设定表示您将使用WEB-INF/resources目录下以application开头,副档名为properties的讯息资源档,在档案名称命名上您可以用语言与国别讯息来表明国际化时要使用的档案,例如application_zh_TW.properties、application_zh_CN.properties、application_zh_HK.properties,储存特定的字元之後,记得使用native2ascii工具程式进行转换,转换方法参考这边:
http://www.caterpillar.onlyfun.net/phpBB2/viewtopic.php?t=1244
Struts预设会以所在伺服器的locale设定来决定要传回哪一个讯息资源档的内容给客户端,如果客户端是浏览器,则会根据所传送的Header Accept-Language来选择传回的资源档讯息,如果浏览器指定的Accept-Language没有对应的资源档案,且也没有与伺服器的locale对应的资源档案,则使用没有任何的语言与国别讯息的讯息资源档案名称,也就是application.properties。
您可以提供application_zh_TW.properties、application_zh_CN.properties、application_zh_HK.properties、application.properties等等的档案,依赖浏览器所提供的Header来选择适当的国际化讯息,但这并不保险,例如若使用者要看中文,而它的浏览器预设语系是英文语系,则这个方法会使得使用者看到英文讯息。
要求使用者有能力自行更改浏览器的预设语系是不切实际的,并不是每个使用者都知道如何作这个设定,更好的方法是使用Struts的session中的Locale来控制,Struts预设会在每个用户的session中放入一个Locale,预设是使用者浏览器所提供的语言讯息,您可以替换掉session中的这个Locale,方法之一是提供一个Action来进行转换,例如在网页上提供一个连结(像是「转为简体」,link至/ChangeLang.do这样),按下後可进行以下的动作:
AccountAction.java
package onlyfun.caterpillar;
import java.util.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class AccountAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
Locale locale = new Locale("zh", "CN");
HttpSession session = request.getSession(true);
session.setAttribute(Action.LOCALE_KEY, locale);
return mapping.findForward("welcome");
}
}
我们在Action中新增一个Locale物件来替换掉session中的Locale物件,之後Struts就使用这个物件来显示指定的讯息资源,例如application_zh_CN.properties中的讯息。
入门 18 - PlugIn介面
您可以继承ActionServlet,重新定义它的init()方法,这样可以在ActionServlet被载入时增加一些您想要载入的资源,然而这并不是个好方法,您必须将程式码写死在当中,如果您要载入的资源多时是一个麻烦,想要拆除某个资源时又是一个麻烦。
Struts 1.1提供一个新的PlugIn介面,它可以让您动态增减ActionServlet的功能,PlugIn中规定了两个必须实作的方法:
PlugIn.java
public interface PlugIn {
public void init(ActionServlet servlet,
ModuleConfig config)
throws javax.servlet.ServletException;
public void destroy();
}
您可以将资源载入的动作撰写在init()方法中,ActionServlet会载入後,会执行实作PlugIn介面的类别之init()方法,例如您可以在这边进行资料库资源的连结,而ActionServlet被终结前,会执行实作PlugIn介面的类别之destroy()方法,您可以在这边撰写一些释放资源的方法。
实作PlugIn介面时,必须提供一个没有参数的建构函式给ActionServlet使用,如果初始化需要一些参数设定,您可以如同JavaBean一样的设定setter,一个实例的例子如下:
MyPlugin.java
package onlyfun.caterpillar;
.......
import org.apache.struts.action.*;
public class MyPlugIn implements PlugIn {
......
public MyPlugIn() {
}
public void init(ActionServlet servlet,
ModuleConfig config)
throws javax.servlet.ServletException {
// 初始化资源
SomeService service = new SomeService();
......
servlet.getServletContext().setAttribute("service", service);;
}
public void destroy() {
// 释放资源
}
public void setXXX(String xxx) {
...
}
}
为了让ActionServlet知道要「挂上」这个PlugIn,您要在struts-config.xml中设定:
struts-config.xml
<plug-in className="onlyfun.caterpillar.MyPlugIn">
<set-property
property="XXX"
value="abcde"/>
</plugin>
如果您在某个Action中必须使用到「挂上」的资源,您可以由Action的Field成员servlet(ActionServlet物件)来取得ServletContext,并取出所必须的资源,例如:
<public ActionForward execute(ActionMapping mapping,
Actionform form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
.......
ServletContext context = servlet.getServletContext();
SomeService service = (SomeService) context.getAttribute("service");
......
}
像Tiles、Validator等都是利用这种方式来扩充Struts的功能。
入门 19 - 简介与设置Validator框架
在使用ActionForm时,我们将表单资料的一些验证工作放在validate()方法中,虽然这是一个可行的方法,然而在ActionForm中撰写特定的验证逻辑会降低ActionForm的重用性,并产生维护上的麻烦,如果要改变验证逻辑,就必须修改原始程式并重新编译。
另一方面,验证工作可以分作客户端与伺服端的工作,客户端可以使用JavaScript作一些基本的栏位验证,像是是否填写所有的栏位,栏位格式是否正确等等,而为了避免客户端直接跳过页面直接请求,伺服端也必须作验证的工作,客户端与伺服端的验证工作可以互相合作。
Jakarta Commons Validator框架可以将验证逻辑移至ActionForm之外,辅助Struts开发人员使用或自订客户端与伺服端的验证工作,Validator与Struts 1.1包装在一起,您也可以至以下的网址单独取得Validator框架:
http://jakarta.apache.org/commons/index.html
要在Struts中使用Validator框架,必须有commons-validator.jar与jakarta-oro.jar两个类别库档案,请将它们复制到应用程式的WEB-INF/lib目录下,而Struts的其它类别库档案commons-beanutils.jar、commons-logging.jar、commons-collections.jar、commons-digester.jar也是必须的,请确定它们都在lib目录之下。
Validator的XML设定可以从struts-blank.war中取得,请将validation.xml与validator-rules.xml两个设定档案复制至WEB-INF/conf目录下,这是我们自己设定,专门用来管理设定档案的目录。
Validator是以plugin的方式来扩充ActionServlet的功能,所以您必须在struts-config.xml中告诉ActionServlet使用这个plugin:
struts-config.xml
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property
property="pathnames"
value="/WEB-INF/conf/validator-rules.xml,/WEB-INF/conf/validation.xml"/>
</plug-in>
pathname设定了validation.xml与validator-rules.xml设定档的位置,ActionServlet初始时,会一并初始Validator框架,载入相关的验证器与验证规则。
Validator框架包使用Struts预设的讯息资源包,建议您复制struts-blank中的application.properties至WEB-INF/classes/resources/中,这个讯息资源包中有一些Validator所预设使用的key-value对应设定。
在下载的Struts档案中,webapps目录下有一个 struts-validator.war,当中包括一些Validator框架的使用范例,您可以将之复制至Servlet容器的webapps下,启动之後会自动解压缩并完成部署,您可以先玩玩当中所实作的验证功能。
入门 20 - Validator客户端验证
validator-rules.xml中包括了一些验证器与验器规则,除了要自订验证器或验证规则之外,基本上您并不需要去变更它。
如果您要进行客户端的验证,可以撰写自己的ActionForm类别,并在validation.xml中定义要验证的属性栏位,一个范例如下:
validation.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">
<form-validation>
<formset>
<form name="userForm">
<field
property="name"
depends="required">
<arg0 key="logon.username.displayname"/>
</field>
<field
property="email"
depends="required,mask">
<arg0 key="logon.email.displayname"/>
<var>
<var-name>mask</var-name>
<var-value>^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*$</var-value>
</var>
</field>
</form>
</formset>
</form-validation>
<form>的name属性指明我们将为哪一个ActionForm作验证,这个名称必须在struts-config.xml中有定义,我们的userForm中包括两个属性name与email,<field>定义要验证的属性栏位,depends定义所使用的验证器,required验证器会检查栏位是否填写,如果没有,它会使用application.properties中的讯息:
application.properties
# -- validator --
errors.invalid={0} is invalid.
.....
# -- display --
logon.username.displayname=Username
logon.email.displayname=Email
您可以将struts-blank中的application.properties复制过来,有 # – validator – 下的验证讯息将为Validator使用,arg0 会替换 {0} 中的文字。
如果depends中指定了两个以上的验证器,则会依序使用,如果有一个验证不通过,则会停止验证并作出相关回应,mask验证器用来验证所输入的栏位是否符合我们的设定格式,使用regular expression来设定验证格式。
Validator的客户端验证是使用JavaScript来进行验证,验证规则定义在validator-rules.xml中,有兴趣的话可以看看当中有关於JavaScript是如何进行验证的,我们可以直接使用<html:javascript/>标签来写出所必须的JavaScript,一个例子如下:
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html>
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<html:errors/>
<html:form action="/LoginAction" focus="name" onsubmit="return validateUserForm(this)">
名称:<html:text property="name" size="20"/><br>
邮件:<html:text property="email" size="20"/><br>
<html:submit/> <html:reset/>
</html:form>
<html:javascript formName="userForm"/>
</body>
</html:html>
在validation.xml的设定上,您可以定义一些常量,以方便在设定过程中使用,例如定义mask的regular expression,例如:
validation.xml
<global>
<constant>
<constant-name>email</constant-name>
<constant-value>^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*$</constant-value>
</constant>
</global>
<formset>
<form name="userForm">
<field
property="name"
depends="required">
<arg0 key="logon.username.displayname"/>
</field>
<field
property="email"
depends="required,mask">
<arg0 key="logon.email.displayname"/>
<var>
<var-name>mask</var-name>
<var-value>${email}</var-value>
</var>
</field>
</form>
<formset>
定义常量在管理上会很方便,日後如果要改变regular expression,就只要更改常量中的设定就好了。
入门 21 - Validator伺服端验证
如果要验证使用者的输入,光使用客户端验证是不保险的,客户端可能跳过输入页面,直接对伺服器发送请求不正确的请求,如果伺服端没有验证请求,就会导致错误。
要使用Validator伺服端验证,只要继承org.apache.struts.validator.ValidatorForm类别来撰写ActionForm就可以了,ValidatorForm是ActionForm的子类别,它重新定义了validate()方法,使用ValidatorForm定义了类别之後,在validation.xml中的设定与前一个主题相同。
ActionServlet会呼叫validate()方法,如果验证有误,则会收集相关的错误讯息并储存在ActionErrors中传回,您要作的是指定struts-config.xml中ActionMapping的type属性,提供一个讯息页面,在当中使用<html:errors/>标签显示错误讯息,例如:
struts-config.xml
<action
path="/LoginAction"
type="onlyfun.caterpillar.LoginAction"
name="userForm"
validate="true"
input="/pages/welcome.jsp">
<forward
name="greeting"
path="/pages/greeting.jsp"/>
</action>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@page contentType="text/html; charset=Big5"%>
<html:html>
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<html:errors/>
<html:form action="/LoginAction" focus="name">
名称:<html:text property="name" size="20"/><br>
邮件:<html:text property="email" size="20"/><br>
<html:submit/> <html:reset/>
</html:form>
</body>
</html:html>
Validator也提供了DynaActionForm的子类别DynaValidatorForm,您只要在struct-config.xml中设定好相关的设定,就可以自动生成ActionForm物件,并具有验证功能,例如:
struts-config.xml
<form-beans>
<form-bean
name="userForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-property
name="name"
type="java.lang.String"
initial="nobody"/>
<form-property
name="email"
type="java.lang.String"
initial="nobody@mail.com"/>
</form-bean>
</form-beans>
入门22 - Validator讯息档管理
Validator框架预设使用Struts讯息资源中的key-value讯息,您可以查看struts-blank中的application.properties,当中有关於validator讯息的部份就是Validator预设使用的:
application.properties
# -- validator --
errors.invalid={0} is invalid.
errors.maxlength={0} can not be greater than {1} characters.
errors.minlength={0} can not be less than {1} characters.
errors.range={0} is not in the range {1} through {2}.
errors.required={0} is required.
errors.byte={0} must be an byte.
errors.date={0} is not a date.
errors.double={0} must be an double.
errors.float={0} must be an float.
errors.integer={0} must be an integer.
errors.long={0} must be an long.
errors.short={0} must be an short.
errors.creditcard={0} is not a valid credit card number.
errors.email={0} is an invalid e-mail address.
Struts支援国际化讯息,Validator同样也支援国际化讯息,您可以在<formset>上使用language、country等属性来指定所要使用的国际化讯息,当然您必须为每一个国际化讯息提供对应的讯息资源档:
validation.xml
<formset>
....
</formset>
<formset language="zh">
....
</formset>
每一个<formset>相对於一个讯息对应,language设定为zh,表示使用application_zh.properties档案中的讯息,如果没有设定,就使用预设的Locale设定。
Validator预设会使用一些key-value讯息对应,您也可以使用自订的讯息来显示验证讯息,例如:
validation.xml
<form name="userForm">
<field
property="name"
depends="required,mask">
<msg
name="mask"
key="logon.username.maskmsg"/>
<arg0 key="logon.username.displayname"/>
<var>
<var-name>mask</var-name>
<var-value>^[a-zA-Z0-9]*$</var-value>
</var>
</field>
</form>
假如您在application.properties中有这么一个key-value对应:
application.properties
logon.username.maskmsg={0} must be letters and numbers, no spaces.
则这个讯息会替换errors.invalid中的讯息(mask验证器预设查找的key-value对应),此时<arg0>会替换{0}中的部份,当然logon.username.displayname在application.properties中也必须有设定。
入门23 - Validator内建验证器
Validator内建了许多基本验证器,以下说明几个常用的验证器作用与基本设定方式:
required验证指定的栏位内容是否填写,可判定只有空白的情况,设定范例如下:
<field property="email" depends="required"/>
mask验证栏位内容是否符合所设定的regular expression,设定范例如下:
<field property="zipCode" depends="mask">
<arg0 key="pre.displayname0"/>
<var>
<var-name>mask</var-name>
<var-value>^"d{5}"d*$</var-value>
</var>
</field>
email检查电子邮件格式,例如:
<field property="mail" depends="email">
<arg0 key="pre.displayname0"/>
</field>
creditCard检查信用卡号码格式,例如:
<field property="cardnumber" depends="creditCard">
<arg0 key="pre.displayname0"/>
</field>
maxLength、minLength验证栏位的字元数是否符合设定的最大长度或最小长度限定,下面是maxLength的设定范例:
<field property="message" depends="maxLength">
<arg0 key="pre.displayname0"/>
<arg1 key="pre.displayname1"/>
<var>
<var-name>maxLength</var-name>
<var-value>100</var-value>
</var>
</field>
range验证栏位的值是否在设定的范围,例如:
<field property="priority" depends="range">
<arg0 key="pre.displayname0"/>
<var>
<var-name>max</var-name>
<var-value>100</var-value>
</var>
<var>
<var-name>min</var-name>
<var-value>0</var-value>
</var>
</field>
date验证栏位内容是否符合日期格式,例如:
<field property="date" depends="date">
<arg0 key="pre.displayname0"/>
<var>
<var-name>datePattern</var-name>
<var-value>MM/dd/yyyy</var-value>
</var>
</field>
datePattern不检查在日期上是否补足0,例如7/23/1978是可以的,如果使用datePatternStrict则要补上0,例如07/23/1978。
byte、short、integer、long、float、double检查栏位值资料型态,例如:
<field property="number" depends="integer">
<arg0 key="pre.displayname0"/>
</field>
入门24 - 自订Validator验证器
看一下validator-rules.xml的内容可以帮助您了解如何自订验证器,以下撷取一个片断作为参考:
validator-rules.xml
<validator name="required"
classname="org.apache.struts.validator.FieldChecks"
method="validateRequired"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionErrors,
javax.servlet.http.HttpServletRequest"
msg="errors.required">
<javascript><![CDATA[
function validateRequired(form) {
........
}
]]>
</javascript>
</validator>
在<javascript>标签之间的,是用来撰写自订客户端验证的JavaScript时使用,要使用它必须在JSP页面的表单提交按钮上增加onsubmit事件处理,例如:
<html:form action="Login" onsubmit="return validateLoginForm(this);">
然後在JSP页面中使用<html:javascript/>标签将所需的JavaScript写出:
<html:javascript formName="loginForm"/>
<javascript>会自动生成JavaScript validateXXXX()方法,其中XXXX是ActionForm名称,在客户端当按下按钮时,validateXXXX()方法会被呼叫并执行客户端的JavaScript验证工作。
在伺服端,您要撰写一个验证类别,该类别必须包括static的validateXXX()方法,并必须包括指定的参数,最後传回true或false表明验证成功或失败,例如:
MyValidator.java
public class MyValidator implements Serializable {
public static boolean validateSomeProperty(java.lang.Object object,
org.apache.commons.validator.ValidatorAction action,
org.apache.commons.validator.Field field,
org.apache.struts.action.ActionErrors errors,
javax.servlet.http.HttpServletRequest request) {
........
}
}
如果验证失败,errors可以让您将一些验证失败讯息加入其中,最後可使用<html:errors/>显示在指定的页面上。
自订Validator时,预设都是找寻application.properties中的key-value对应,如果需要额外的讯息,可以在该档案中加入。
有关更多自订验证器的方式,可以参考validator-rules.xml与Validator的原始码档案。
入门25 - 简介与设置Tiles
Tiles是一个Template Engine,它可以让网页的配置(Layout)标签与内容分离,提供一个版面管理机制。
在网页上最常用来处理版面的是表格,透过将表格边框设定为0,可以用它来切割版面,并在当中置入内容,然而一大堆版面配置的标签将与内容夹杂在一起(个人就不太喜欢处理表格的标签),如果以後要更改内容或版面配置,会是一个麻烦。
在视窗程式的设计中存在有版面管理员(Layout Manager)机制,我们可以选择一个版面管理机制,之後直接在视窗容器上添加元件而不用处理版面配置细节,而由版面管理员来自己处理元件的版面配置,如果想要改变版面配置,则只要直接更换版面管理员,所有的元件将自动依新的管理员而重新配置版面。
Tiles提供一个类似於视窗程式版面管理的机制,透过定义档,您可以定义版面配置,以及当中所要置入的内容网页,内容与版面配置的标签可以分离,您可以随时抽换每一个内容网页,也可以重用每一个版面配置。
在Struts 1.1中,Tiles随著它一同发布,您可以直接从struts-blank中开始设定,当中已经包括了Tiles必要的档案与设定,如果您要自行设定 Tiles,您必须要有commons-beanutils.jar、commons-logging.jar、commons- collections.jar、commons-digester.jar、struts.jar四个档案在您的WEB-INF/lib目录中,并且在 struts-config.xml中加入以下的内容:
struts-config.xml
<plug-in className="org.apache.struts.tiles.TilesPlugin" >
<set-property property="definitions-config"
value="/WEB-INF/conf/tiles-defs.xml" />
<set-property property="moduleAware" value="true" />
<set-property property="definitions-parser-validate" value="true" />
</plug-in>
在Struts 1.1中,您只要使用PlugIn就可以挂上Tiles的功能,而不用再定义您的ActionServlet,definitions-config属性中的tiles-defs.xml是Tiles的定义档,Tiles提供了一个处理请求的RequestProcessor,可以进行一些forward 与include的额外动作,在struts-config.xml中预设是使用它:
struts-config.xml
<controller
processorClass="org.apache.struts.tiles.TilesRequestProcessor"/>
这两个在struts-config.xml加入的位置,您可以参考struts-blank中的设定;最後,您要将struts- tiles.tld放置在WEB-INF/tld下,并在web.xml中定义,这样可以使用Tiles标签,例如在web.xml中是这么设定的:
web.xml
<taglib>
<taglib-uri>/tags/struts-tiles</taglib-uri>
<taglib-location>/WEB-INF/tld/struts-tiles.tld</taglib-location>
</taglib>
完成以上之後,您就可以使用Tiles的功能了,Tiles的定义档可以使用XML或JSP页面来完成,这将在之後的主题中加以说明。
入门26 - 使用XML管理Tile配置资源
在Tiles中,您可以使用XML或是JSP来管理版面配置的相关资源,这边先介绍使用XML定义档的方式,如之前主题所定义的,我们的定义档是tiles-defs.xml,我们在当中如下撰写:
tiles-defs.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/tiles-config_1_1.dtd">
<tiles-definitions>
<definition name=".myLayout" path="/tiles/myLayout.jsp">
<put name="title" value="Sample Page Title" />
<put name="header" value="/tiles/header.jsp" />
<put name="menu" value="/tiles/menu.jsp" />
<put name="footer" value="/tiles/footer.jsp" />
<put name="body" value="/tiles/body.jsp" />
</definition>
</tiles-definitions>
在<definition>标签中,我们指定了版面配置的JSP页面,也就是path属性所指定的myLayout.jsp,name属性表明这个版面配置的名称,而在接下来的<put>标签中,我们指定了内容页面的名称与JSP页面实际的位置。
Tiles的定义档可以支援国际化,您可以复制以上的内容,撰写在tiles-defs_zh.xml中,并将encoding改为big5,就可以在定义档中使用中文,例如:
tiles-defs_zh.xml
<?xml version="1.0" encoding="big5" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/tiles-config_1_1.dtd">
<tiles-definitions>
<definition name=".myLayout" path="/tiles/myLayout.jsp">
<put name="title" value="Tiles范例" />
<put name="header" value="/tiles/header.jsp" />
<put name="menu" value="/tiles/menu.jsp" />
<put name="footer" value="/tiles/footer.jsp" />
<put name="body" value="/tiles/body.jsp" />
</definition>
</tiles-definitions>
其中myLayout.jsp的内容如下:
myLayout.jsp
<%@taglib prefix="tiles" uri="/tags/struts-tiles"%>
<html>
<head><title><tiles:getAsString name="title"/></title></head>
<body>
<table border="0" width="100%" cellspacing="5">
<tr>
<td colspan="2"><tiles:insert attribute="header"/></td>
<tr>
<tr>
<td width="140" valign="top"><tiles:insert attribute="menu"/></td>
<td valign="top" align="left"><tiles:insert attribute="body"/></td>
</tr>
<tr>
<td colspan="2"><tiles:insert attribute="footer"/></td>
</tr>
</table>
</body>
</html>
这个版面配置网页中主要使用表格来切割版面,Tiles标签可以从attribute属性得知对应於XML定义档的真正JSP页面,例如header将被实际取代为header.jsp的内容,您可以自行完成其它的JSP页面内容。
接下我们可以开始使用定义好的版面配置,例如在welcome.jsp中如下撰写:
welcome.jsp
<%@page contentType="text/html; charset=big5"%>
<%@taglib prefix="tiles" uri="/tags/struts-tiles"%>
<tiles:insert definition=".myLayout" flush="true"/>
我们在<tiles:insert>标签中指定我们将使用.myLayout的版面配置定义,如果您连上welcome.jsp,则实际上您会看到以下的内容:
<html>
<head><title>Tiles范例</title></head>
<body>
<table border="0" width="100%" cellspacing="5">
<tr>
<td colspan="2">
<center><h1>Tiles测试范例<h1>
</td>
<tr>
<tr>
<td width="140" valign="top">
选单一<br>
选单二<br>
选单三<br>
</td>
<td valign="top" align="left">
Tiles提供一个类似於视窗程式版面管理的机制。
</td>
</tr>
<tr>
<td colspan="2">
<center>版权所有(c) http://www.caterpillar.onlyfun.net/phpBB2/
</td>
</tr>
</table>
</body>
</html>
如果您将来要改变版面配置的方式,您可以直接修改tiles-defs.xml中path所指向的Layout页面,就如同您在视窗程式中改变版面管理员一样,如果您要修改指向的内容网页,也只要更改tiles-defs.xml中相对应的设定,所有的更改集中於Tiles设定档中加以管理,达到将版面配置与内容分离的目的。
您可以扩充某个定义,重新定义当中所管理的页面资源,一个例子如下:
tiles-defs.xml
<?xml version="1.0" encoding="big5" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/tiles-config_1_1.dtd">
<tiles-definitions>
<definition name=".myLayout" path="/tiles/myLayout.jsp">
<put name="title" value="Tiles范例" />
<put name="header" value="/tiles/header.jsp" />
<put name="menu" value="/tiles/menu.jsp" />
<put name="footer" value="/tiles/footer.jsp" />
<put name="body" value="/tiles/body.jsp" />
</definition>
<definition name=".myLayout2" extends=".myLayout">
<put name="title" value="Tiles范例二" />
<put name="header" value="/tiles/header2.jsp" />
</definition>
</tiles-definitions>
在上面的定义中,.myLayout2扩充自.myLayout,并重新定义了title与header两个属性,其它未重新定义的属性将直接继承自.myLayout。
Comments (Hide)
入门28 - 使用JSP管理Tiles配置资源
您可以在JSP页面中直接定义管理版面配置资源,这样的作法比较简单,且可以藉由重新定义来达到再用定义档的目的,使用上更有弹性,例如我们可以这么定义一个JSP页面来管理版面配置资源:
<%@page contentType="text/html; charset=Big5"%>
<%@taglib prefix="tiles" uri="/tags/struts-tiles"%>
<tiles:definition id="myLayout" page="/tiles/myLayout.jsp">
<tiles:put name="title" value="Tiles范例" />
<tiles:put name="header" value="/tiles/header.jsp" />
<tiles:put name="menu" value="/tiles/menu.jsp" />
<tiles:put name="footer" value="/tiles/footer.jsp" />
<tiles:put name="body" value="/tiles/body.jsp" />
</tiles:definition>
要使用这个JSP定义档,可以使用include的方式来达到重复使用的目的,例如:
<%@taglib prefix="tiles" uri="/tags/struts-tiles"%>
<%@include file="/tiles/definition1.jsp"%>
<tiles:insert beanName="myLayout" flush="true"/>
读取页面时,Tiles会建立一个定义档物件,id为物件的名称,而之後我们使用beanName来指定定义档物件的名称。
如果您要在某个页面中重新定义内容网页指向的位置,可以重新定义某些属性值,例如:
<%@taglib prefix="tiles" uri="/tags/struts-tiles"%>
<%@include file="/tiles/definition1.jsp"%>
<tiles:insert beanName="myLayout" flush="true">
<tiles:put name="title" value="Tiles范例二" />
<tiles:put name="header" value="/tiles/header2.jsp" />
</tiles:insert>
我们也可以不经由定义档而直接使用某个Layout页面,例如:
<%@page contentType="text/html; charset=Big5"%>
<%@taglib prefix="tiles" uri="/tags/struts-tiles"%>
<tiles:insert page="/tiles/myLayout.jsp" flush="true">
<tiles:put name="title" value="Tiles范例" />
<tiles:put name="header" value="/tiles/header.jsp" />
<tiles:put name="menu" value="/tiles/menu.jsp" />
<tiles:put name="footer" value="/tiles/footer.jsp" />
<tiles:put name="body" value="/tiles/body.jsp" />
</tiles:insert>
这么作的好处是简单方便,可以直接使用Layout页面而无法额外定义管理档案,而缺点就是无法再重用这个页面的定义内容。
入门29 - 档案上传
使用浏览器进行档案上传时,是使用multipart/form-data编码,然而 Servlet容器并不会自动帮我们处理编码,而必须由程式设计人员自行处理,在这个部份,Struts中提供了档案上传的套件,使用它就可以轻易的解决档案上传的问题,首先请确定commons-fileupload.jar有在WEB-INF/lib目录下。
与档案上传相关的类别是在org.apache.struts.upload套件下,首先我们撰写简单的ActionForm,以接收上传的档案:
UploadForm.java
package onlyfun.caterpillar;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.upload.*;
public class UploadForm extends ActionForm {
private FormFile file;
public void setFile(FormFile file) {
this.file = file;
}
public FormFile getFile() {
return file;
}
public void reset(ActionMapping mapping, HttpServletRequest req) {
file = null;
}
}
档案经由表单上传至伺服器之後,会储存为FormFile型态的物件,您可以藉由这个物件来取得上传档案的相关讯息,例如 getContentType()、getFileName()、getFileSize()等等,getFileData()可以取得档案的位元阵列资料,getInputStream()可以取得InputStream型态之物件,我们撰写一个简单的Action,将接收到的档案储存下来:
UploadAction.java
package onlyfun.caterpillar;
import java.io.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.upload.*;
public class UploadAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
UploadForm fileForm = (UploadForm) form;
FormFile file = fileForm.getFile();
FileOutputStream fileOutput = new FileOutputStream("/home/caterpillar/files/" + file.getFileName());
fileOutput.write(file.getFileData());
fileOutput.flush();
fileOutput.close();
return mapping.findForward("success");
}
}
至於JSP网页的部份,我们可以这么撰写表单:
<html:form action="/Upload" method="post" enctype="multipart/form-data">
选择档案:<html:file property="file" />
<html:submit>上传</html:submit>
</html:form>
剩下的就是配置struts-config.xml中的ActionForm与Action对应了,相信对现在的您来说已经不难了,这部份请参考之前的主题。