ActionForm和Bean一样以属性名匹配的映射机制从HTTP请求中填充对象数据,但ActionForm比一般的Bean提供了更多的功能,Struts允许ActionForm通过validate()方法进行自校验,当数据不合法时自动转向到输出界面,此外还可以通过reset()方法,在数据填充前复位属性值。
下面我们就来创建UserActionForm,替换User的功能,建立起Struts框架中的"数据模型"。
1.指定ActionForm的Web模块及类信息 File->New...->Web->在Web页中双击ActionForm图标,弹出如图 6所示的对话框:
图 6 创建UserActionForm ·Struts config:我们前面有提到Struts1.1支持多个配置文件,所以你在这儿可以选择使用哪个Struts配置文件。因为我们现在还没有定义多个Struts配置文件,所以只得使用struts-config.xml。在开发新增图书的功能时,我们将定义另一个配置文件。 ·ActionForm:ActionForm的类名,这里我们填入UserActionForm。 按Next到下一步。 2.定义ActionForm属性 图 7 定义ActionForm属性 通过Add...按钮为UserActionForm增加4个属性,如图 7所示。特别的,如果这个ActionForm所对应的入口页面已经创建,你也可以直接通过Add from JSP...按钮,选择一个JSP页面,JBuilder会分析这个页面的表单,并将表单的数据组件名抽取为ActionForm的属性。
按Next到下一步。
3.一些附加功能的定义
图 8 附加功能定义 在FormBeanName中为UserActionForm指定一个名字,一般接受JBuilder所提供的默认名即可。这个名字将在struts-config.xml文件用来命名UserActionForm。 ActionForm比一般JavaBean强大的地方在于它可以进行数据检验,还可以进行数据复位。如果这个ActionForm最终要放到session中的,那么最好实现reset()方法,以复位ActionForm的数据,否则属性可能不会反映最新的值。这里, UserActionForm无需进行数据有效性校验,但由于UserActionForm最终需要放到session中,所以我们需要实现reset()方法。故此我们勾选Create/replace reset() method body选项。
直接按Finish创建UserActionForm,再将User类的代码拷贝过来,整改后的最终代码如下所示:
代码清单 6 以ActionForm实现的User类
1. package bookstore;2. 3. import java.sql.*;4. import java.text.*;5. import java.util.Date;6. import javax.servlet.http.*;7. import org.apache.struts.action.*;8. 9. public class UserActionForm 10. extends ActionForm 11. {12. private String userId;13. private String password;14. private String userName;15. private String loginDatetime;16. public String getPassword() {17. return password;18. }19. 20. …21. //复位所有属性值22. public void reset(ActionMapping actionMapping,HttpServletRequest servletRequest) {23. this.userId = null;24. this.userName = null;25. this.password = null;26. this.loginDatetime = null;27. }
此外,JBuilder自动在struts-conf.xml文件中通过<form-bean>描述ActionForm。UserActionForm必须和一个Action相关联,因为HTTP请求通过Struts总控制器转发给Action,Struts控制器一旦发现Action有一个对应的ActionForm时,就用HTTP请求的数据填充这个ActionForm。
用Action代替switch.jsp的控制转换功能 我们在前面已经数落用switch.jsp实现请求转换控制的缺点,Struts框架的Action是实现请求转换控制的最适合替代者。 在这节里,我们就来创建一个名为LoginAction的Action,让其完美的接替switch.jsp的工作。 File->New...->Web->在Web页中双击Action图标,启动创建Action的向导。 1.指定Action名字及Web模块
图 9 指定Web模块及Action名字 在Action中键入LoginAction作为Action的类名,其中Base class的下拉框中有许多Action基类可供选择,它们用于不同的场合,这些选项是: ·org.apache.struts.action.Action:标准的Action。 ·org.apache.struts.actions.ForwardAction:相当于JSP的<jsp:forward>,方便Struts控制器进行预处理。此外,从学究的角度上来说,在JSP页面直接通过<jsp:forward>违反了MVC的分层原则,控制器无法干预。 ·org.apache.struts.actions.IncludeAction:出于ForwardAction相似的原因,Struts推荐用IncludeAction代替JSP的<jsp:include>。 ·org.apache.struts.actions.LookupDispatchAction:如果一个表单有多个提交按钮,不同的提交按钮执行不同的业务操作,用DispatchAction最为合适。 ·org.apache.struts.actions.SwitchAction:用SwitchAction可在不同的Struts模块间转换。 由于我们的Action需要完成用户密码验证的业务,并根据结果转换到不同的页面中,所以这个LoginAction是一个普通的Action,故我们选择org.apache.struts.action.Action。 按Next到下一步。 2.设置Action的配置信息 图 10 设置LoginAction的配置信息 ·Action path:访问这个Action的URI,接受默认的/loginAction,这样我们将通过类似这样的URL:http://127.0.0.1:8080/webModule/loginAction.do来访问这个Action。 ·FormBean name:下拉框中列出Web模块中所有的ActionForm,我们选择前一小节中所创建的userActionForm。这样客户端的HTTP请求访问LoginAction时,HTTP请求所带的数据就会被Struts总控制器自动填充到userActionForm中了。 ·Scope:Action有两个选择:request和session。表示ActionForm在填充后将放在request对象中还是session对象中,由于我们需要在通过密码验证后,才使用户登录系统。这样就不能使userActionForm在数据填充时就放入session中,而应该在通过密码验证后,手工将其绑定到session中(UserActionForm一旦绑定到session中,其valueBound()方法就会被调用,记录用户登录日志),故此,我们选择request。 ·input JSP:输入的JSP页面。在ActionForm需要进行数据有效性自校验的情况下,如果校验失败,Struts框架总控制器将请求返回到这个输入页面上。因为UserActionForm无需进行有效性校验(在3.1的第3步我们没有为UserActionForm实现自校验功能),所以无需指定输入的JSP。 按Finish按钮直接创建LoginAction,JBuilder自动打开Struts Config Editor,生动形象地展现用户登录模块Struts框架下的处理流程,如图 11所示: 图 11 Struts Config Editor 位于中心的/loginAction是访问LoginAction的URI,它是登录业务的控制器。Struts总控制器创建一个UserActionForm实例,并用HTTP请求的数据填充UserActionForm实例,然后将其传给LoginAction的execute()方法。 3.定义访问入口 现在我们需要调整login.jsp表单的提交地址,使用LoginAction来处理用户登录的请求,调整后的代码如所示: 代码清单 7 login.jsp 使用LoginAction处理用户登录1. <%@page contentType="text/html; charset=GBK" import="bookstore.UserList" %>2. …3. <form name="form1" method="post" action="/webModule/loginAction.do">用户名:4. <select name="userId">5. <option value="" selected>--登录用户--</option>6. <%=UserList.getUserListHTML()%>7. </select>8. 密 码:9. <input name="password" type="password">10. <input type="submit" name="Submit" value="登录">11. </form>12. </body>13. </html>
如第3行所示,将原来action="switch.jsp"改为"/webModule/loginAction.do",由于我们需要将整个应用部署于/webModule的URI下,所以需要在Action访问的地址前加上/webModule。如果通过Struts的<html:form>标签来指定表单提交的地址,则无需添加/webModule,标签将自动进行转换,你将在本专题后续内容中学习到这种方法。注意: Struts框架总控制器Servlet通过路径匹配的方式截获HTTP请求,其匹配串是*.do,表示URL以.do结束的HTTP请求才会被Struts框架处理,否则Struts忽略之。所以在写链接地址时千万不要忘了调用地址后加一个.do的后缀。
对login.jsp做调整后,重新切换到/loginAction的Struts Config Editor中,你将看到如图 12所示的流程图: 图 12 在JSP中指定调用Action后的流程图 JBuilder将分析Web模块中所有JSP文件,如果发现引用了/loginAction就将其添加到该图中来,作为其访问入口。
4.为/loginAction定义两个出口 一个Action一般只有一个入口,但往往会有多个出口,Action根据业务处理的不同结果转向相应的出口。图 12 /loginAction右边是一个带"forward"的浅色虚框,右键单击这个forward虚框,在弹出的菜单中点击Add Forward菜单项,在Strut Config Editor中将新增一个默认名为forward的出口项图标,左键单击这个forward新增的图标,对这个出口进行制定,如图 13所示:
图 13 为Action定义出口 我们为这个出口地址取名为success,点击Path后的…按钮弹出Browser For Path对话框,列出当前Web模块所有可作为出口地址,如图 14所示: 图 14 可选出口地址 我们选择welcome.jsp作为success的出口地址,按OK确定。 按相同的方法再为/loginAction创建一个名为fail出口地址为fail.jsp以及名为error出口地址为error.jsp两出口,最后登录模块的流程如图 15所示: 图 15 登录模块的最终流程 后面,我们将在LoginAction通过代码根据用户验证成功与否决定程序的出口,你将会发现我们通过出口的名字引用出口的地址。 完成以上配置后,切换到Source标签页,struts-config.xml文件中悉数记录下了这个配置信息: 代码清单 8 登录模块对应struts-config.xml的配置信息1. <struts-config>2. <form-beans>3. <form-bean name="userActionForm" type="bookstore.UserActionForm" />4. </form-beans>5. <action-mappings>6. <action name="userActionForm" path="/loginAction"7. scope="request" type="bookstore.LoginAction">8. <forward name="success" path="/welcome.jsp" />9. <forward name="fail" path="/fail.jsp" />10. <forward name="error" path="/error.jsp" />11. </action>12. </action-mappings>13. </struts-config>
其中第3行的配置信息声明了UserActionForm,为其指定了一个名字,在第6~10行是/loginAction的配置信息,它通过name属性声明这个Action对应的ActionForm为UserActionForm。 在第8~10行,3个出口各对应一个<forward>配置项,在LoginAction中我们将通过<forward>的name属性引用出口的地址。 下面,我们调整LoginAction类的execute()方法的代码,在其中验证用户密码,并根据验证结果转向不同的出口,其最终代码如下所示: 代码清单 9 LoginAction.java1. package bookstore;2. 3. import org.apache.struts.action.ActionMapping;4. import org.apache.struts.action.ActionForm;5. import javax.servlet.http.HttpServletRequest;6. import javax.servlet.http.HttpServletResponse;7. import org.apache.struts.action.ActionForward;8. import org.apache.struts.action.Action;9. 10. import java.sql.*;11. 12. public class LoginAction13. extends Action {14. public ActionForward execute(ActionMapping actionMapping,15. ActionForm actionForm,16. HttpServletRequest servletRequest,17. HttpServletResponse servletResponse) {18. UserActionForm userActionForm = (UserActionForm) actionForm;19. Connection conn = null;20. try21. {22. conn = DBConnection.getConnection();23. PreparedStatement pStat = conn.prepareStatement(24. "select USER_NAME from T_USER where USER_ID=? and password = ?");25. pStat.setString(1, userActionForm.getUserId());26. pStat.setString(2, userActionForm.getPassword());27. ResultSet rs = pStat.executeQuery();28. if (rs.next())29. { //密码正确30. userActionForm.setUserName(rs.getString(1));31. servletRequest.getSession().setAttribute("ses_userBean", userActionForm);32. return actionMapping.findForward("success");//通过验证,转向welcome.jsp出口33. }34. }35. catch (SQLException se)36. {37. se.printStackTrace();38. return actionMapping.findForward("error");//程序发生异常,转向error.jsp出口39. }40. finally41. {42. try43. {44. if (conn != null)45. {46. conn.close();47. }48. }49. catch (SQLException ex)50. {51. ex.printStackTrace();52. return actionMapping.findForward("error");//程序发生异常,转向error.jsp出口53. }54. }55. return actionMapping.findForward("fail");//未通过验证,转向fail.jsp出口56. }57. }
在第18行通过强制类型转换获取UserActionForm实例,其后验证用户的代码其实就是switch.jsp验证用户的scriptlet的代码。我们根据用户验证的结果通过ActionMapping将请求转向不同的出口(如第32、38、52、55行所示),其中findForward(name)中的name即是struts-config.xml中对应Action配置项的<forward>中指出的出口项名字。这种通过名字引用出口的调用方式给我们带来了很大的灵活性,因为它将流程逻辑和具体实现隔离开来,假设你不希望用welcome.jsp作为登录成功所转向的页面,你只要在配置文件中对success的出口配置项进行调整就可以了,而无需更改程序。 在通过用户验证后,我们将userActionForm手工放到session中(第31行),以ses_userBean为名放入session时userActionForm的valueBound()方法会被触发调用,记录用户登录日志。由于原success.jsp包含下面的代码:<jsp:useBean id="ses_userBean" scope="session" class="bookstore.User"/>
因为此时,我们已经用UserActionForm替换原User类,所以需要对这行代码作调整,否则在进行强制类型转换时将发生ClassCastException异常。调整后的代码为:<jsp:useBean id="ses_userBean" scope="session" class="bookstore.UserActionForm"/>
提示: 一般情况下,Action只执行流程控制的功能,而不执行具体的业务处理。所以LoginAction的execute()中验证用户的业务最好抽取到一个具体的BO中(Business Object:商业处理对象)。