在《 JBuilder 2005 Servlet高级开发》中我们介绍了用户登录模块的流程,其中switch.jsp充当一个业务处理和页面转发的中心处理器。由于JSP的设计初衷是实现页面展现逻辑,而这里我们居然"倒行逆施",将JSP用作业务处理和页面转发,显然是不恰当的。我们原来的switch.jsp身兼数职:接收login.jsp页面的 表单数据,查询数据库,转发页面,象一个事无巨细,有僭越之嫌的 "管家"总揽了所有的事情,程序没有分层,逻辑显得非常不清晰,象个大杂烩。
其实Servlet本身倒是比较适合开发switch.jsp所完成的功能,但在《 JBuilder 2005 Servlet高级开发》专题中,我们并没有用Servlet斧正之,是因为我们希望在本专题中从更高的角度更完美地替换switch.jsp。
可以通过Struts框架对switch.jsp功能进行分解,switch.jsp通过<jsp:useBean>获取登录页面表单数据可以通过ActionForm机制实现;switch.jsp查询T_USER数据表,判断用户是否是合法用户可以在Action的execute()中处理;当未通过验证转向fail.jsp页面,如果发生异常转向error.jsp页面,当用户通过验证后转向welcome.jsp页面,可以在Action中通过ActionForward实现多出口切换。
新增一个图书管理模块
现实的图书管理模块,应该包括对图书进行增、删、改、查的功能,由于篇幅所限,我们仅提供图书新增的功能。数据库中必须提供一张表用于保存图书的信息,这张表名为T_BOOK,其结构如图 4所示:
图 4 T_BOOK表结构 |
我们完全用Struts框架实现图书新增的功能,JBuilder 提供了一个优秀的Struts可视化设计工具(Struts Config Editor),其中Action Designer能够非常形象地描述出一个业务功能操作流程,我们不妨就通过这个可视化设计器预览图书新增业务的程序结构,如图 5所示:
图 5 新增图书Struts流程 |
·bookAdd.jsp:图书 录入的界面,在这儿你将学习到Struts标签和资源文件的知识。
·bookActionForm:由BookActionForm类实现,保存bookAdd.jsp表单提交的数据。在这儿,你将学习到如何创建ActionForm,ActionForm如何对数据有效性进行自检的知识。
·/bookInsertAction:由BookInsertAction类实现,即业务控制器,负责将bookActionForm中的新增图书保存到数据库T_BOOK表中,并导向到insertSuccess.htm页面。
事前准备
通过File->New Project...创建一个名为bookStore的新工程,并在工程下创建名为webModule的Web模块,将原《JBuilder 2005 Servlet高级开发》专题的代码覆盖该新工程对应的内容。
用SQL Plus运行下面的SQL 语句创建T_BOOK表:
代码清单 5 创建T_BOOK表的SQL语句
1. create table T_BOOK ( 2. BOOK_ID VARCHAR2(10) not null, 3. ISBN VARCHAR2(20) not null, 4. BOOK_NAME VARCHAR2(50) not null, 5. AUTHOR VARCHAR2(50), 6. CREATE_DATE CHAR(8), 7. constraint PK_T_BOOK primary key (BOOK_ID) 8. ); |
从这个SQL中,我们可以看出BOOK_ID是表的主键,所以不允许重复,而ISBN和BOOK_NAME两字段不允许为空。它们决定了在图书添加时的数据检验规则。
同样的,我们需要将必要的类添加到这个新工程的类库中。
用ActionForm截获登录表单数据
原登录模块用bookstore.User描述login.jsp登录页面表单的数据,在switch.jsp程序中通过<jsp:useBean>标签获取login.jsp表单的数据。其实User类相当于Struts框架中的模型,我们将通过一个ActionForm更好地实现这个功能。
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.java
1. 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 LoginAction 13. 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. try 21. { 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. finally 41. { 42. try 43. { 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"/> |
提示: 新增一个Struts配置文件
然后,在工程窗格的资源树中定位到webModule->Deployment descriptors-><Struts 1.1>节点上,右击<Struts 1.1>节点,在弹出的菜单中选择Properties...弹出Properties for ’<Struts 1.1>’对话框,如图 16所示:
点击Add...按钮,在弹出的Choose Struts config file对话框中选择book-struts-config.xml配置文件,按OK这个新的Struts配置文件将添加到Struts config file in web.xml列表中。 新增配置文件成功后,在工程窗格资源树的<Struts 1.1>节点下,你将会发现这个新加入的Struts配置文件,如下图所示:
这样,在创建新的FormBean或Action时,你就可以选择用哪个配置文件来保存Struts的配置信息了。 图书Action Form 下面我们着手创建用于接收新增图书页面表单数据的BookActionForm,使用book-struts-config.xml保存BookActionForm的配置信息。BookActionForm需要进行数据有效性自检,也就是说,要让BookActionForm实现validate()方法。 创建BookActionForm和创建UserActionForm相似,但是在向导的第1步需要指定book-struts-config.xml记录BookActionForm配置信息,如图 18所示:
我们在前一节为Web模块添加了一个配置文件,在Struts config下拉框中列出了Web模块所有配置文件,这里我们选择WEB-INF/book-struts-config.xml。 在向导的第2步,我们为BookActionForm定义下列5个属性:
在向导的第2步直接按Finish创建BookActionForm。由于bookId属性是主键,所以不能和T_BOOK中已有的记录重复,这可以通过BookActionForm的数据自检机制来完成,数据自检是通过定义validate()方法来实现的。向导已经为BookActionForm生成了validate()方法框架,我们只需要在validate()方法编写bookId的校验的代码就可以了,BookActionForm的最终代码如代码清单 10所示: 代码清单 10 BookActionForm.java
当用户提交表单后,Struts框架自动把表单数据填充到ActionForm中,接着Struts框架自动调用ActionForm的validate()方法进行数据验证。如果validate()方法返回的ActionErrors为null或不包含任何ActionMessage对象,表示通过验证,Struts框架将ActionForm和HTTP请求一起传给Action的execute(),否则Struts框架将HTTP请求返回到输入的页面中,而输入页面即可通过<html:errors>显示对应request域中的ActionErrors错误信息显示出来。 此外,我们在reset()方法中将createDate属性置为当前的日期,因为这个属性值不是通过页面表单提供的。 |
新增图书JSP页面
1.通过向导创建bookAdd.jsp
通过JSP向导创建bookAdd.jsp页面,在向导的第2步选择使用Struts1.1的struts-bean和struts-html标签,如图 19所示:
图 19 指定选用Struts标签 |
2.使用JBuilder的Struts标签构建JSP页面
你可以直接用拖拽的方法从JBuilder编辑器左边的标签库将Struts标签添加到JSP页面中,如图 20所示:
图 20 用拖拽的方式添加Struts标签 |
Struts的html标签可以完成和标准的HTML元素相同的功能,Struts提倡使用Struts html标签库,因为这些标签可以和Struts框架的其他组件紧密地联系起来。而Strtus的bean标签库可以访问已经存在的JavaBean及其属性,有一些bean标签还可以访问HTTP请求头信息及Web资源文件的信息。
我们希望用Struts的html标签库创建添加图书的表单,通过bean标签库访问Web资源文件作为表单组件前的标识文字。
bookAdd.jsp的最终代码如代码清单 11所示:
代码清单 11 bookAdd.jsp
1. <%@page contentType="text/html; charset=GBK" %> 2. <%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> 3. <%@taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> 4. <html> 5. <head> 6. <title>bookInsert</title> 7. <script language="JavaScript" > 8. function mySubmit(form) 9. { 10. if(form.isbn.value == null || form.isbn.value == "") 11. { 12. alert("图书的ISBN不允许为空"); 13. return false; 14. } 15. if(form.bookName.value == null || form.bookName.value == "") 16. { 17. alert("图书名不允许为空"); 18. return false; 19. } 20. } 21. </script> 22. </head> 23. <body bgcolor="#ffffff"> 24. <html:errors/> 25. <html:form action="/bookInsertAction.do" focus="bookId" method="post" 26. οnsubmit="return mySubmit(this)" > 27. <table width="100%%" border="0"> 28. <tr> 29. <td> 30. <bean:message bundle="bookstore" key="bookstore.bookId"/> 31. </td> 32. <td> 33. <html:text name="bookActionForm" property="bookId"/> 34. </td> 35. <td> 36. <bean:message bundle="bookstore" key="bookstore.isbn"/> 37. </td> 38. <td> 39. <html:text name="bookActionForm" property="isbn"/> 40. </td> 41. </tr> 42. <tr> 43. <td> 44. <bean:message bundle="bookstore" key="bookstore.bookName"/> 45. </td> 46. <td> 47. <html:text name="bookActionForm" property="bookName"/> 48. </td> 49. <td> 50. <bean:message bundle="bookstore" key="bookstore.author"/> 51. </td> 52. <td> 53. <html:text name="bookActionForm" property="author"/> 54. </td> 55. </tr> 56. <tr align="center"> 57. <td colspan="4"> 58. <html:submit value="保存"/> 59. <html:reset value="取消"/> 60. </td> 61. </tr> 62. </table> 63. </html:form> 64. </body> 65. </html> |
其中第25~63行是表单的定义代码,将<html:form>的action指定为"/bookInsertAction.do", 它是BookInsertAction的访问URI,将在下一节实现,通过<html:form>访问Action时,action只需保证和配置文件中指定的path一致就可以了,无需在前面添加上诸如/webModule的Web部署子目录。
在第26行我们为<html:form>指定了一个onsubmit客户端校验函数,当isbn和bookName两组件中的任何一个为空时,拒绝提供表单。
我们定义了4个<html:text>,它们对应标签HTML的<input type="text">输入框标签,其中name属性为对应的ActionForm名字,而property对应ActionForm的属性。图 21是bookAdd.jsp的设计期效果图:
图 21 bookAdd.jsp设计时的界面图 |
当然,你可以直接在表单组件前写入具体的标识,如"图书ID",而非第30行的<bean:message>标签,但后者通过一个资源文件产生具体的标识,这样不但可直接通过资源文件控制标识还提供了国际化的特性。
上面,我们只是简单地引用了名为bookstore的资源文件,下面我们需要创建这个资源文件并在Struts配置文件中描述它。
到<工程目录>/src目录下,创建一个名为bookStoreResource_zh_CN.properties的资源文件,其内容如下所示:
bookstore.bookId=/u56fe/u4e66IDbookstore.isbn=ISBNbookstore.bookName=_ /u56fe/u4e66/u540dbookstore.author=/u4f5c/u8005bookstore.dbaccess.error=_ /u6570/u636e/u5e93/u8bbf/u95ee/u9519/u8befbookstore.duplicate.bookId=_ /u56fe/u4e66/u7f16/u53f7/u548c/u6570/u636e/u5e93/u4e2d/u5df2/u6709/u7684/u7f16/u53f7/u91cd/u590d |
这儿的中文必须采用Unicode编码格式,其对应的中文文件的内容为:
bookstore.bookId=图书IDbookstore.isbn=ISBNbookstore.bookName=图书名bookstore.author=作者bookstore.dbaccess.error=数据库访问错误bookstore.duplicate.bookId=图书编号和数据库中已有的编号重复 |
在编译工程时,JBuilder会将位于<工程目录>/src下的资源文件拷贝到Web模块的WEB-INF/classes目录下。
提示: JDK提供了一个将中文转换为Unicode编码格式的工具native2ascii.exe,它位于<JDK>/bin/目录下。在DOS命令窗口下,通过native2ascii -encoding GBK <源文件> <目标文件>即可以完成转换。 |
注意: 在前文中,我们曾提到了JBuilder 2005的一个Bug,即Web模块中的类或资源有时得不到同步,需要手工将类和资源拷贝到对应的目录。如果你发现资源文件没有同步到WEB-INF/classes目录时,bookStoreResource_zh_CN.properties需要在编译工程后手工拷贝到这个目录下,否则Struts就无法找到资源了。 |
在工程窗格的资源树中找到book-struts-config.xml双击打开其对应的Struts Config Editor,切换到Message Resources标签页,如所示:
图 22 定义资源文件 |
通过Add...按钮定义一个键名为bookstore的bookStoreResource资源文件。不同的语言环境的客户端将会访问不同的资源文件,如客户端为中文环境时,将访问bookStoreResource_ch_CN.properties,如果是英语的客户端将访问bookStoreResource_cn.properties资源文件,如果没有找到对应语言的资源文件,将访问默认的资源文件,这里,默认资源文件为bookStoreResource.properties。<bean:message>的boudle即为Parameter栏定义的名称。
实战经验:
笔者原来对Struts标签是比较排斥的,因为虽说它本意希望将页面展现逻辑和程序逻辑完美地分离开来。页面设计的工作由页面设计人员在Dreamweaver中完成,由于Dreamweaver不认识Struts的标签,那些像<html:text>,<html:submit>等表单组件的Struts标签在页面设计时看不到效果,所见即所得的理念被无情的剥夺了,界面设计人员对包含Struts标签的页面常常感到如堕五里雾中。
许多Struts开发人员一直梦寐以求,希望能得到一个Dreamweaver的Struts标签扩展插件,让页面设计人员可以象标准的HTML组件标签一样设计页面。FWA公司的Visual Tags for Struts插件终于使我们梦想成真。通过这一插件,可以将Dreamweaver完美地和Struts结合起来,使用Struts标签的JSP页面可以在设计期得到使用标准HTML标签一样的可视化效果。你可以通过http://www.fwasi.com/downloads/下载Visual Tags for Struts的试用版。图 21的bookAdd.jsp效果界面,即是在Dreamweaver 2004中使用了Visual Tags for Struts插件后的效果页面。
此外,有一点关于Struts标签的事情也必须提及:Sun开发出了JSF页面标签,这和Struts的标签在功能上是有重叠的,Struts 以后的版本将逐渐往JSF靠近,JSF的标签可能将最终取代Struts自己的标签,以实现天下大统。
创建BookInsertAction
下面,我们来创建BookInsertAction,在该Action中将图书记录添加到T_BOOK表中。如果操作成功定向到insertSuccess.htm操作成功页面,如果在进行数据库操作时发现SQLException,则转向sqlFail.htm页面。我们需要事先创建这两个HTML页面,为了简单,仅在其中写入报告操作成功和失败的信息即可。
按3.2相似的方式创建BookInsertAction,用book-struts-config.xml记录配置信息,在向导的第2步,将FormBean name指定为bookActionForm,Scope为request,将input JSP指定为/bookAdd.jsp,如图 23所示:
图 23 指出BookInsertAction的配置信息 |
按Finish直接创建BookInsertAction,JBuilder将打开Struts Config Editor显示/bookInsertAction的流程,如图 24所示:
图 24 bookInsertAction流程 |
添加1个出口,名为success,路径为/insertSuccess.htm。最终的/bookInsertAction的流程如图 5所示。
代码清单 12是BookInsertAction的代码,它完成图书添加,出口控制的操作:
代码清单 12 BookInsertAction.java
1. package bookstore; 2. 3. import javax.servlet.http.*; 4. import org.apache.struts.action.*; 5. import java.sql.*; 6. 7. public class BookInsertAction 8. extends Action { 9. public ActionForward execute(ActionMapping actionMapping, 10. ActionForm actionForm, 11. HttpServletRequest servletRequest, 12. HttpServletResponse servletResponse) 13. throws Exception 14. { 15. BookActionForm bookActionForm = (BookActionForm) actionForm; 16. Connection conn = null; 17. conn = DBConnection.getConnection(); 18. PreparedStatement pStat = conn.prepareStatement( 19. " insert into T_BOOK1(BOOK_ID,ISBN,BOOK_NAME,AUTHOR,"+ 20. "CREATE_DATE) values(?,?,?,?,?)"); 21. pStat.setString(1, bookActionForm.getBookId()); 22. pStat.setString(2, bookActionForm.getIsbn()); 23. pStat.setString(3, bookActionForm.getBookName()); 24. pStat.setString(4, bookActionForm.getAuthor()); 25. pStat.setString(5, bookActionForm.getCreateDate()); 26. pStat.executeUpdate(); 27. return actionMapping.findForward("success"); 28. 29. } 30. } |
BookInsertAction将bookActionForm的数据通过JDBC添加到T_BOOK表中,添加成功则转向insertSuccess.htm页面。有些观察细致的读者也许已经注意到BookInsertAction的execute()方法并未直接对SQLException进行处理,而是将异常抛出,如第13行所示。这里,我们要用到Struts1.1的新功能:通过配置方式处理异常。
在工程窗格的webModule/Deployment descriptors/<Struts 1.1>下找到并双击book-struts-config.xml文件,调出的Struts Config Editor配置编辑器,切换到Global Exceptions标签页,如图 25所示:
图 25 异常处理配置 |
点击Add...定义一个名为sqlexception的异常处理配置项,处理java.sql.SQLException异常,定义完这个配置项后,选中这个配置项,点击Edit...切换到这个配置项的详细设置页面,如图 26所示:
图 26 异常处理配置窗口 |
在窗口下部切换到Source视图页中,这个异常配置项的配置信息如代码清单 13所示:
代码清单 13 SQLException的异常处理配置项
1. … 2. <struts-config> 3. … 4. <global-exceptions> 5. <exception key="sqlexception" type="java.sql.SQLException" 6. path="/sqlFail.htm"/> 7. </global-exceptions> 8. … 9. </struts-config> |
第5~6行将/sqlFail.htm和java.sql.SQLException"挂接"起来,这样程序中任何Action的execute()方法抛出的SQLException异常都将由sqlexception配置项处理。
在welcome.jsp页面中添加一个调用bookAdd.jsp的链接,登录系统后,通过这外链接调出bookAdd.jsp页面,如图 27所示:
图 27 添加图书页面 |
填写图书记录,点击保存提交表单到/bookInsertAction的Action中,如果提供的图书ID和T_BOOK中已有的BOOK_ID重复,则Struts总控制器将直接返回到bookAdd.jsp页面中,由<html:errors/>报告错误信息。如果数据通过BookActionForm的自检并成功添加到数据库中,将最终转向insertSuccess.htm页面。
提示: 如果用Tomcat为Web服务器,如果图书记录会出现中文乱码的问题,可以使用我们上面介绍过的一个编码过滤器,你可以在web.xml中配置这个过滤器,乱码问题就可以解决了。 |