这里是我们Struts应用的核心配置文件struts-config.xml,它把各种动作,动作表单,请求以及验证关联在一起形成可用的Web应用的黏合剂,这部分内容相信大家有Struts的底子,会很容易看懂:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">
<struts-config>
<!-- form-beans -->
<form-beans>
<form-bean name="reservationForm"
type="org.leno.struts.action.ReservationForm" />
</form-beans>
<!-- action-mappings -->
<action-mappings>
<action path="/reservation"
type="org.apache.struts.actions.ForwardAction" name="reservationForm"
scope="request" parameter="/hotelReservation.jsp" />
<action path="/validateReservation"
type="org.leno.struts.action.ValidateReservationAction"
name="reservationForm" validate="true"
input="/jsp/validation/reservationErrors.jsp">
<forward name="valid" path="/jsp/validation/blank.jsp" />
</action>
<action path="/saveReservation"
type="org.leno.struts.action.SaveReservationAction"
name="reservationForm" validate="true"
input="/hotelReservation.jsp">
<forward name="success"
path="/jsp/validation/reservationSuccessful.jsp" />
<forward name="fail" path="/hotelReservation.jsp" />
</action>
</action-mappings>
<!-- message-resources -->
<message-resources parameter="org/leno/struts/ApplicationResources" />
<!-- plug-in -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml" />
</plug-in>
</struts-config>
在这个应用中只有一个动作表单:ReservationForm类,它是一个扩展了ValidatorActionForm类的简单Javabean风格的对象,它为每个表单元素提供了公共的获取方法和设置方法。细心的同志已经发现了,ReservationForm并不是扩展ValidatorForm类,而是扩展ValidatorActionForm类。那么,这两种扩展有什么区别呢?简单点说,扩展ValidatorActionForm意味着validation.xml中的验证规则会基于请求的路径应用到请求上;扩展ValidatorForm意味着validation.xml中的验证规则会基于请求使用的表单bean应用到请求上。下面是ReservationForm的代码:
package org.leno.struts.action;
import java.text.*;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.*;
import org.apache.struts.validator.ValidatorActionForm;
public class ReservationForm extends ValidatorActionForm {
private String arrivalDate;
private String departDate;
private String smokingPref;
private String requests;
private String name;
private String telephone;
private DateFormat parser = new SimpleDateFormat("MM/dd/yyyy");
public String getArrivalDate() {
return arrivalDate;
}
public Date getArrivalDateAsDate() {
try {
return parser.parse(arrivalDate);
}
catch(ParseException e) {
return null;
}
}
public void setArrivalDate(String arrivalDate) {
this.arrivalDate = arrivalDate;
}
public Date getDepartDateAsDate() {
try {
return parser.parse(departDate);
}
catch(ParseException e) {
return null;
}
}
public String getDepartDate() {
return departDate;
}
public void setDepartDate(String departDate) {
this.departDate = departDate;
}
public String getSmokingPref() {
return smokingPref;
}
public void setSmokingPref(String smokingPref) {
this.smokingPref = smokingPref;
}
public boolean isSmokingRequest() {
return smokingPref.equalsIgnoreCase("smoking");
}
public String getRequests() {
return requests;
}
public void setRequests(String requests) {
this.requests = requests;
}
public ActionErrors validate(ActionMapping mapping
, HttpServletRequest request) {
ActionErrors errors;
//validate framework's common validator
errors = super.validate(mapping, request);
//validate continute...
DateFormat parser = new SimpleDateFormat("MM/dd/yyyy");
try {
Date arrival = parser.parse(arrivalDate);
Date departure = parser.parse(departDate);
if(departure.before(arrival)) {
errors.add(ActionErrors.GLOBAL_MESSAGE
, new ActionMessage("errors.departure.before.arrival"
, true));
}
}
catch (Exception e) {
// Do nothing -- date format validation is handled in
// validation.xml.
}
return errors;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
}
ValidateReservation动作的input属性指向reservationErrors.jsp文件,它使用Struts标签来生成验证过程中的错误信息。这里大家要停下来好好梳理一下,大家还记得主jsp页面上有一个id属性为errors的div标签吧,如果验证有错误产生,Struts就会返回input所指定的资源作为响应。这里客户端是用Ajax技术异步访问这个动作,所以响应的错误信息会出现在div里面。也就是说,只有页面上div里一小部分会更新,整个页面并不会被重绘。(回头看看那段js代码!)下面就是需要生成的全部输出:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
<%@taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<logic:messagesPresent>
<ul>
<html:messages id="error">
<li style="color:red">
<bean:write name="error"/>
</li>
</html:messages>
</ul>
</logic:messagesPresent>
当然,如果验证成功,一切正常,valid的路径就是blank.jsp:
这个文件几乎不包括任何内容,只有一个非间断空白(nonbreaking space),下面是我们自定义的验证文件validation.xml:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules
Configuration
"http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<form-validation>
<global>
<constant>
<constant-name>phoneFormat</constant-name>
<constant-value>
^/(?/d{3}/)?/s|-/d{3}-/d{4}$
</constant-value>
</constant>
<constant>
<constant-name>dateFormat</constant-name>
<constant-value>^/d{1,2}//d{1,2}//d{4}$</constant-value>
</constant>
</global>
<formset>
<form name="/validateReservation">
<field property="arrivalDate" depends="required, mask">
<msg key="errors.date" name="mask" />
<arg0 key="label.arrival.date" resource="true" />
<arg1 key="format.date" />
<var>
<var-name>mask</var-name>
<var-value>${dateFormat}</var-value>
</var>
</field>
<field property="departDate" depends="required, mask">
<msg key="errors.date" name="mask" />
<arg0 key="label.depart.date" resource="true" />
<arg1 key="format.date" />
<var>
<var-name>mask</var-name>
<var-value>${dateFormat}</var-value>
</var>
</field>
</form>
<form name="/saveReservation">
<field property="arrivalDate" depends="required, mask">
<msg key="errors.date" name="mask" />
<arg0 key="label.arrival.date" resource="true" />
<arg1 key="format.date" />
<var>
<var-name>mask</var-name>
<var-value>${dateFormat}</var-value>
</var>
</field>
<field property="departDate" depends="required, mask">
<msg key="errors.date" name="mask" />
<arg0 key="label.depart.date" resource="true" />
<arg1 key="format.date" />
<var>
<var-name>mask</var-name>
<var-value>${dateFormat}</var-value>
</var>
</field>
<field property="smokingPref" depends="required">
<arg0 key="label.smoking.pref" resource="true" />
</field>
<field property="name" depends="required">
<arg0 key="label.name" resource="true" />
</field>
<field property="telephone" depends="required, mask">
<msg key="errors.invalid.telephone.format" name="mask" />
<arg0 key="label.telephone" resource="true" />
<var>
<var-name>mask</var-name>
<var-value>${phoneFormat}</var-value>
</var>
</field>
</form>
</formset>
</form-validation>
接下来是我们的最重要的Action类以及服务层的Service类:
package org.leno.struts.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.*;
import org.leno.struts.service.ReservationService;
public class ValidateReservationAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm actionForm
, HttpServletRequest request, HttpServletResponse response)
throws Exception {
ReservationForm form = (ReservationForm) actionForm;
ReservationService service = new ReservationService();
boolean isAvailable =
service.isReservationAvailable(form.getArrivalDateAsDate()
, form.getDepartDateAsDate()
, form.isSmokingRequest());
ActionMessages errors = this.getErrors(request);
if(!isAvailable) {
errors.add(ActionMessages.GLOBAL_MESSAGE
, new ActionMessage("errors.reservation.not.available"
, true));
}
saveErrors(request, errors);
ActionForward forward = mapping.findForward("valid");
if(errors.size() > 0) {
return mapping.getInputForward();
}
return forward;
}
}
ReservationService.java:
package org.leno.struts.service;
import java.util.Date;
import java.util.Random;
public class ReservationService {
private static Random random = new Random();
public boolean isReservationAvailable(Date arrival, Date departure,
boolean isSmoking) {
// Of course a real implementation would actually check if the desired
// reservation was available. Here, just do it randomly so the
// reservation is unavailable about 1/3 of the time.
return !((random.nextInt(100) % 3) == 0);
}
public void saveReservation(Date arrival, Date departure,
boolean isSmoking, String requests, String name, String telephone)
throws Exception {
if (!isReservationAvailable(arrival, departure, isSmoking)) {
throw new Exception();
}
// Logic to actually save the reservation goes here.
}
}
在这里,大家可以把验证框架看成服务器端数据验证的第一道防线,无需编写任何Java代码,就可以通过编写XML文件涵盖Web应用的很大一部分验证。还记得在ReservationForm里面errors = super.validate(mapping, request);这一行代码吗?非常关键。这道防线布下之后,我们在ReservationForm里面的validate方法就是第二道防线,它保证了用户输入的到达日期在离开日期之前。而哪怕是这样,我们的验证并没有结束,我们还有第三道防线,那就是ValidateReservationAction里面的execute方法,它可以访问服务层验证是否有满足用户请求的到达和离开时间以及吸烟嗜好的房间。恩,结构大体上有点清晰了,我们来理一理。客户端通过几个控件的onblur事件触发向服务器端ValidateReservationAction的异步访问,而这个动作在struts-config里面配置为需要验证,所以三道防线启动,只要有一处验证出现错误,马上返回reservationErrors.jsp里的内容对div进行局部刷新,如果没有错误,会用一个空白占位符清空原先的错误信息。在这里,Ajax和Struts的集成验证的优势体现的淋漓尽致——想预订宾馆房间的用户只需要填充前面3个表单字段,就可以知道自己需要的房间是否可以得到满足,而不是提交整个表单之后!大家把前后几个文件联系起来看,就可以理解了。最关键的部分完成后,下面的事情就是水到渠成了。
package org.leno.struts.action;
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;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.leno.struts.service.ReservationService;
public class SaveReservationAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm actionForm,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
String forward = "success";
ReservationForm form = (ReservationForm) actionForm;
ReservationService service = new ReservationService();
try {
service.saveReservation(form.getArrivalDateAsDate(), form
.getDepartDateAsDate(), form.isSmokingRequest(), form
.getRequests(), form.getName(), form.getTelephone());
} catch (Exception e) {
ActionMessages errors = this.getErrors(request);
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
"errors.reservation.not.available", true));
saveErrors(request, errors);
forward = "fail";
}
return mapping.findForward(forward);
}
}
这是表单提交后访问的Action,里面就是普通的Struts应用,一样会做表单验证的工作,如果验证成功,就会给客户端呈现下面的页面reservationSuccessful.jsp:
<%@page contentType="text/html" pageEncoding="UTF-8" isELIgnored="false"%>
<%@taglib uri="http://struts.apache.org/tags-bean" prefix="bean"%>
<!DOCTYPE html PUBLIC "-//W
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><bean:message key="reservation.success.title" />
</title>
</head>
<body>
<h1>
<bean:message key="reservation.success.msg" />
</h1>
<ul>
<li>
<bean:message key="label.arrival.date" />
: ${reservationForm.arrivalDate}
</li>
<li>
<bean:message key="label.depart.date" />
: ${reservationForm.departDate}
</li>
<li>
<bean:message key="label.smoking.pref" />
: ${reservationForm.smokingPref}
</li>
<li>
<bean:message key="label.specialRequests" />
: ${reservationForm.requests}
</li>
<li>
<bean:message key="label.name" />
: ${reservationForm.name}
</li>
<li>
<bean:message key="label.telephone" />
: ${reservationForm.telephone}
</li>
</ul>
</body>
</html>
呵呵,这样,我们的所有代码工作就完成了,大家可以按照上面的阐述把例子跑出来看下效果,的下篇文章我们一起做一下总结。