1.8 信息发布模块设计
1.8.1 信息发布模块概述
单击页面顶部的“发布信息”超链接,将进入信息发布页面。在该页面中,用户可从下拉列表中选择一种信息类别(共包括11个信息类别:公寓信息、招聘信息、求职信息、培训信息、家教信息、房屋信息、车辆信息、求购信息、出售信息、招商引资、寻找启示),然后输入其他信息,如图1.38所示。
信息录入完整后,单击“发布”按钮,即可发布信息。此时,程序会先验证用户是否输入了信息,若验证失败,则返回信息发布页面,进行相应提示;若验证成功,则会继续验证输入的“联系电话”和E-mail格式是否正确;若该验证成功,则向数据库中插入记录,完成发布操作;信息发布成功后,返回给用户信息的ID值。发布的信息还需要管理员进行审核,只有审核成功的信息才能显示在前台页面中。信息发布的流程如图1.39所示。

图1.38 信息发布页面 图1.39 信息发布流程
1.8.2 信息发布技术分析
信息发布技术所要实现的是将用户填写的数据保存到数据表中。要实现这样一个目的,首先要解决在Struts 2.0中如何获取表单数据以及如何验证表单数据的问题。下面分别进行介绍。
1.如何获取表单数据
在Struts 2.0中不存在与表单对应的ActionForm,而是直接在处理类中设置与表单字段对应的属性,并为属性创建setXXX()与getXXX()方法来获取、返回表单数据。
下面以应用Struts 2.0实现一个简单的用户登录为例介绍如何获取表单数据。当用户输入的用户名为tsoft、密码为111时,则登录成功,返回到welcome.jsp页面,显示用户输入的用户名和密码。
首先,创建一个请求处理类LoginAction,表单请求被提交到该类中进行处理,为了能够获取表单数据,需要创建与表单字段对应的属性并设置它们的setXXX()与getXXX()方法。LoginAction类的具体代码如下:
package com.action;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport {
private String userName; //对应表单中的“用户名”字段
private String userPass; //对应表单中的“密码”字段
private String message; //用来保存提示消息
……//省略了属性的setXXX()与getXXX()方法
public String execute() {
if(userName.equals("tsoft")&&userPass.equals("111")) {
message="登录成功!";
return "yes";
}else{
message="登录失败!";
return "no";
}
}
}

然后,创建登录页面login.jsp,在该页面中应用Struts 2.0标签来创建一个Form表单、文本输入框、密码输入框和“登录”、“重置”按钮,运行效果如图1.40所示。
login.jsp页面的关键代码如下:
<%@ taglib uri="/struts-tags" prefix="s" %>
u <s:form action="login.action" theme="simple">
<table border="0">
<tr>
<td>用户名:</td>
v <td><s:textfield name="userName"/></td>
</tr>
<tr>
<td>密 码:</td>
<td><s:password name="userPass"/></td>
</tr>
</table>
</s:form>
U 代码贴士
u form标签用于生成一个表单,其action属性指定请求路径,若该路径以“.action”为后缀,则会到Struts 2.0的配置文件中查找与之对应的配置,根据配置将请求转发给对应的Action类进行处理;将theme属性值设为simple,可以取消其默认的表格布局。
v textfield标签表示文本输入框,其name属性指定了该文本框与表单处理类中对应的属性userName。实际上,textfield标签的name属性值并不是必须与处理类中的属性具有相同的名称。如上述代码,当表单提交后,会自动调用处理类中的setUserName()方法和setUserPass()方法将表单数据赋值给类中指定的属性,因此该属性的命名是任意的,如命名为myName。不过为了便于理解,通常情况下都是将属性与表单字段设置为相同的名称,读者也应按照该规则命名。
password标签表示密码输入框,其用法同v。
其次,在配置文件中对表单所请求的路径进行配置。配置代码如下:
<package name="login" extends="struts-default">
<action name="login" class="com.action.LoginAction">
<result name="yes">welcome.jsp</result> <!-- 配置登录成功后返回的页面 -->
<result name="no">welcome.jsp</result> <!-- 配置登录失败后返回的页面 -->
</action>
</package>
关于Struts 2.0配置文件的介绍,读者可查看1.14.2节。
接下来,创建登录操作后的提示页面welcome.jsp,在该页面中输出用户登录结果,并输出用户输入的用户名和密码。welcome.jsp页面的关键代码如下:
<%@ taglib uri="/struts-tags" prefix="s" %>
<b><s:property value="message"/></b>
<table>
<tr>
<td>
用户名:<b><s:property value="userName"/></b>--
密 码:<b><s:property value="userPass"/></b>
</td>
</tr>
</table>
welcome.jsp页面是从LoginAction处理类中进行请求转发来访问的,只有在这种情况下,property标签采用如上用法时,才能输出LoginAction类中message、userName和userPass属性的值;否则若是通过地址栏或超链接直接访问welcome.jsp页面,如上用法的property标签将不输出任何值。
最后,分别在“用户名”和“密码”输入框中输入“tsoft”和“111”,单击“登录”按钮,将出现如图1.41所示的运行结果。
若输入的数据为“yxq”和“123”,则出现如图1.42所示的运行结果。

图1.41 登录成功 图1.42 登录失败
Struts 2.0还允许将封装表单数据的代码从Action类中分离出来,写在另一个JavaBean中。例如,将上述例子进行如下修改。
首先,创建一个存储表单数据的JavaBean。代码如下:
package com.model;
public class User {
private String userName; //对应表单中的“用户名”字段
private String userPass; //对应表单中的“密码”字段
……//省略了属性的setXXX()与getXXX()方法
}
然后,创建处理类LoginAction。代码如下:
package com.action;
import com.model.User;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport {
private User user;
private String message;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
……//省略了message属性的setXXX()与getXXX()方法
public String execute() {
if(user.getUserName().equals("tsoft")&&user.getUserPass().equals("111")) {
message="登录成功!";
return "yes";
}else{
message="登录失败!";
return "no";
}
}
}
其次,修改login.jsp页面。修改部分的代码如下:
<tr>
<td>用户名:</td>
<td><s:textfield name="user.userName"/></td>
</tr>
<tr>
<td>密 码:</td>
<td><s:password name="user.userPass"/></td>
</tr>
Struts配置文件不需要修改,接下来修改welcome.jsp文件。
<%@ taglib uri="/struts-tags" prefix="s" %>
<font size="3"><b><s:property value="message"/></b></font>
<table border="0">
<tr>
<td>
用户名:<b><s:property value="user.userName"/></b>--
密 码:<b><s:property value="user.userPass"/></b>
</td>
</tr>
</table>
最后,分别在“用户名”和“密码”文本框中输入“tsoft”和“111”,运行结果与图1.41所示相同。
2.Struts 2.0中的表单验证
在Struts 2.0中可使用校验框架和Action类中的验证方法来对表单数据进行验证,本系统采用的是第二种方法。
Action类中的验证方法的命名规则为validateXXX(),其中XXX表示Action类中用来处理请求的某个方法名称。当请求被转发给Action类时,该Action会根据用户请求来调用相应的方法处理请求,若在这之前需要进行表单数据验证,则可实现与该方法对应的validateXXX()验证方法进行验证。
例如,本系统中用来处理前台操作的Action类中的Add()方法用来处理信息发布的请求,在Add()方法中需要编写向数据表中插入记录的代码,所以在这之前需要验证用户输入的表单数据是否为空,可在Action类中实现validateAdd()方法进行验证,验证成功后,会自动调用Add()方法。
validateXXX()验证方法不需要返回值,在方法中可将提示信息通过addFieldError()方法进行保存,这样,返回验证失败的提示页面后,就可通过fielderror标签输出提示信息。
Struts 2.0将根据是否调用了addFieldError()方法判断验证是否成功,若validateXXX()方法的程序流程执行了addFieldError()方法,则验证失败,那么在validateXXX()方法的流程结束后,将返回到配置文件中指定的JSP页面。
例如,本系统在配置文件中对登录操作进行的配置如下:
<action name="login_*" class="com.yxq.action.AdminAction" method="{1}">
<result name="input">/pages/admin/Login.jsp</result>
<result name="login">/pages/admin/view/AdminTemp.jsp</result>
<result name="logout" type="redirectAction">index</result>
</action>
其中加粗的代码就是对表单验证失败时的配置,此时<result>元素的name属性值必须为input,/pages/admin/ogin.jsp则表示验证失败后返回的页面。
3.解决Struts 2.0中的中文乱码问题
在Struts 2.0中解决中文乱码问题,可通过一种简单的方法实现。在应用的WEB-INF/classes目录下创建一个struts.properties资源文件,Struts 2.0默认会加载WEB-INF/classes目录下的该文件,在该文件中进行如下编码:
struts.i18n.encoding=gb2312
其中struts.i18n.encoding指定了Web应用默认的编码。
1.8.3 信息发布实现过程
信息发布用到的数据表:tb_info。
用户通过单击页面顶部的“发布信息”超链接,进入信息发布页面,在该页面中填写发布信息后,提交表单,在InfoAction处理类中获取表单数据进行验证,验证成功后向数据表中插入数据,完成信息的发布。下面按照这个操作流程,介绍信息发布的实现过程。
1.实现页面顶部的“发布信息”超链接
在view目录下的top.jsp文件中实现进入信息发布页面的“发布信息”超链接。代码如下:
例程41 代码位置:光盘\TM\01\view\top.jsp
<a href="info_Add.action?addType=linkTo" style="color:gray">[发布信息]</a>
该超链接请求的路径为info_Add.action,根据在Struts配置文件中的配置,由InfoAction类中的Add()方法处理该请求,参数addType通知Add()方法当前请求的操作,其值为linkTo表示仅仅是连接到信息发布页面;若为add,则表示向数据表中插入记录。
2.创建发布信息的addInfo.jsp页面
在信息发布页面中包含一个表单,该表单中的元素如表1.12所示。
表1.12 信息发布页面所涉及的表单元素
|
名 称 |
元 素 类 型 |
重 要 属 性 |
含 义 |
|
addType |
<input type="hidden"> |
name |
通过该表单元素,InfoAction类的Add()方法判断要进行的操作 |
|
infoSingle.infoType |
<s2:select> |
name、list |
信息类别下拉列表框 |
|
infoSingle.infoTitle |
<s2:textfield> |
name |
信息标题 |
|
infoSingle.infoContent |
<s2:textarea> |
name |
信息内容 |
|
infoSingle.infoPhone |
<s2:textfield> |
name |
联系电话 |
|
infoSingle.infoLinkman |
<s2:textfield> |
name |
联系人 |
|
infoSingle.infoEmail |
<s2:textfield> |
name |
E-mial地址 |
addInfo.jsp页面的关键代码如下:
例程42 代码位置:光盘\TM\01\pages\add\addInfo.jsp
<%@ taglib prefix="s2" uri="/struts-tags" %>
<s2:form action="info_Add.action" theme="simple">
<input type="hidden" name="addType" value="add"/>
<tr>
<td>信息类别:</td>
<td> <s2:select emptyOption="true" list="#session.typeMap" name="infoSingle.infoType"/></td>
<td>[信息标题最多不得超过 40 个字符] </td>
</tr>
<tr> <td colspan="3"><s2:fielderror><s2:param value="%{'typeError'}"/></s2:fielderror></td></tr>
<tr>
<td>信息标题:</td>
<td colspan="2"><s2:textfield name="infoSingle.infoTitle"/></td>
</tr>
<tr><td colspan="3"><s2:fielderror><s2:param value="%{'titleError'}"/></s2:fielderror></td></tr>
……//省略了实现其他表单字段的代码
</s2:form>
U 代码贴士
u select标签用来实现下拉列表框,emptyOption属性取值为true,表示第一个下拉列表项为空白,取值为false或省略该属性,则不生成空白列表项;list属性则指定用来生成下拉列表项的数据源,若该数据源是一个Map对象,则默认的会将该Map对象的key值作为列表项的值(在程序中使用),将value值作为列表项的标签(显示给用户);name 属性指定了与表单的处理类中对应的setXXX()与getXXX()方法。
v fielderror标签用来输出通过Action类的addFieldError()方法保存的信息,param标签则指定要输出保存的哪条信息。如果要输出保存的全部信息,可使用<s2:fielderror/>。“%{}”用来计算表达式,被计算的表达式写在“{}”中,如<s2:property value="%{100+1}"/>,将输出“101”,所以,代码中为param标签的value属性指定的是字符串值typeError,若写为<s2:param value="typeError"/>,则此时的typeError相当于一个页面变量。例如:<s2:set name="myError" value="%{'typeError'}"/><s2:param value="myError"/>与<s2:param value="%{'typeError'}"/>实现的功能是相同的。
3.在InfoAction类中实现处理信息发布请求的方法
例程42中指定表单所触发的请求为info_Add.action,根据例程34中cityinfo.xml文件的配置,表单将被提交到InfoAction类的Add()方法中进行处理,在这之前需要进行表单验证。下面先来创建验证表单的方法。
þ 创建验证表单的validateAdd()方法。
在该方法中,先获取表单数据,然后依次进行验证。首先验证用户输入是否为空,在都不为空的情况下,再来验证输入的“联系电话”和E-mail格式是否正确。在验证过程中,若验证失败,则调用addFieldError()方法保存提示信息。validateAdd()方法的代码如下:
例程43 代码位置:光盘\TM\01\src\com\yxq\action\InfoAction.java
public void validateAdd(){
int type=infoSingle.getInfoType(); //获取信息类别表单数据
String title=infoSingle.getInfoTitle(); //获取信息标题表单数据
String content=infoSingle.getInfoContent(); //获取信息内容表单数据
String phone=infoSingle.getInfoPhone(); //获取联系电话表单数据
String linkman=infoSingle.getInfoLinkman(); //获取联系人表单数据
String email=infoSingle.getInfoEmail(); //获取E-mail地址表单数据
boolean mark=true;
if(type<=0){
mark=false;
addFieldError("typeError",getText("city.info.no.infoType")); //getText(String key)方法用来获取properties 资源文件中key指定的键值存储的内容
}
……//省略了其他表单数据的验证
if(mark){ //若表单数据都不为空
……//省略了验证联系电话和E-mail格式的代码
}
}
þ 创建处理请求的Add()方法。
表单验证成功后,调用Add()方法处理请求。在该方法中先获取表单数据,然后生成SQL语句,最后调用OpDB类对象的OpUpdate()方法向数据表中插入记录,完成信息发布。Add()方法的代码如下:
例程44 代码位置:光盘\TM\01\src\com\yxq\action\InfoAction.java
public String Add(){
String addType=request.getParameter("addType"); //获取访问该方法的请求要进行的操作
if(addType==null||addType.equals("")){
request.setAttribute("mainPage","/pages/add/addInfo.jsp");
addType="linkTo";
}
if(addType.equals("add")){ //执行信息发布操作
request.setAttribute("mainPage","/pages/error.jsp");
OpDB myOp=new OpDB();
Integer type=Integer.valueOf(infoSingle.getInfoType()); //获取信息类别
String title=infoSingle.getInfoTitle(); //获取信息标题
String content=DoString.HTMLChange(infoSingle.getInfoContent()); //转换信息内容中的HTML字符
String phone=infoSingle.getInfoPhone(); //获取联系电话
phone = phone.replaceAll(",","●"); //替换“,”符号
String linkman=infoSingle.getInfoLinkman(); //获取联系人
String email=infoSingle.getInfoEmail(); //获取E-mail地址
String date=DoString.dateTimeChange(new java.util.Date()); //获取当前时间并转换为字符串格式
String state="0"; //设置已审核状态为0
String payfor="0"; //设置已付费状态为0
Object[] params={type,title,content,linkman,phone,email,date,state,payfor};
String sql="insert into tb_info values(?,?,?,?,?,?,?,?,?)";
int i=myOp.OpUpdate(sql,params); //调用业务对象的OpUpdate()方法向数据表中插入记录
if(i<=0) //操作失败
addFieldError("addE",getText("city.info.add.E")); //保存失败提示信息
else { //操作成功
sql="select * from tb_info where info_date=?"; //生成查询刚刚发布信息的SQL语句
Object[] params1={date};
int infoNum=myOp.OpSingleShow(sql, params1).getId(); //获取刚刚发布信息的ID值
addFieldError("addS",getText("city.info.add.S")+infoNum); //保存成功提示信息
}
}
return SUCCESS;
}
4.配置cityinfo.xml文件
对信息发布请求的配置,与列表显示某类别中所有信息请求的配置是同一个配置,可参看例程34。
1.8.4 单元测试
在进行软件开发的过程中,避免不了出现错误或未发现的Bug,这些错误和Bug发现得越早,对后面的开发和维护越有利,因此测试在软件开发的过程中显得越来越重要。软件测试通常可分为单元测试、综合测试和用户测试,其中单元测试是程序员在开发过程中最为常用的。
1.单元测试概述
具体来说,单元就是指一个可独立完成某个操作的程序元素,通常为方法或过程,所以单元测试,就是针对这个方法或过程进行的测试。但通常情况下,几乎很少存在不与其他方法发生调用与被调用关系的方法,所以也可将对一组用来完成某个操作的方法或过程进行的测试称为单元测试。
对单元的理解可归纳为以下几点:
þ 不可再分的程序模块。
þ 该模块实现了一个具体的功能。
þ 实现了某一功能的模块,与程序中其他模块不发生关系。
对于面向过程的语言来说,如C语言,进行的单元测试一般针对的是函数或过程,而像Java这种面向对象的语言,通常是针对类进行单元测试。
对单元测试的理解可归纳为以下几点:
þ 它是一种验证行为。
程序中的每一项功能都可以通过单元测试来验证它的正确性。它为以后的开发提供支持,就算是开发后期,也可以轻松地增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西;而且它为代码的重构提供了保障。这样,开发员可以更自由地对程序进行改进。
þ 它是一种设计行为。
编写单元测试将使开发员从调用者的角度观察、思考。特别是先写测试,迫使开发人员把程序设计成易于调用和可测试的。
þ 它是一种编写文档的行为。
单元测试是一种无价的文档,它是展示类或函数如何使用的最佳文档。这份文档是可编译、可运行的,并且永远保持与代码同步。
2.单元测试带来的好处
þ 对于开发人员来说,进行单元测试可以大大减少程序的调试时间及程序中的Bug。
þ 对于整个项目来说,减少了调试时间,缩短了项目开发周期。对项目中的模块进行单元测试后,保证项目最后交付给用户进行测试时有可靠依据。
þ 对于测试人员来说,减少了反馈的问题。
þ 最主要的是,为项目的后期维护带来了很大的方便,并可减少后期维护的费用。
3.JUnit单元测试工具的介绍与使用
JUnit是程序单元测试的框架,专门用于测试Java开发的程序。同类产品还包括NUnit(.Net)、CPPUnit(C++),都属于xUnit中的成员。目前JUnit的最新版本是JUnit 4.4。在Eclipse开发工具中已经集成了JUnit的多个版本,本节将介绍如何在Eclipse中使用JUnit进行单元测试。在介绍JUnit的使用之前,先来看一下测试成功与失败后的运行结果,如图1.43和图1.44所示。

图1.43 单元测试成功

图1.44 单元测试失败
下面介绍如何在Eclipse中使用JUnit进行单元测试。
(1)在Eclipse中新建一个Java项目。
(2)右击项目,在弹出的快捷菜单中选择“构建路径/添加库”命令,在弹出的“添加库”对话框中选择JUnit选项,如图1.45所示。
(3)单击“下一步”按钮,在弹出的“JUnit库”对话框中选择JUnit库版本为JUnit4,单击“完成”按钮,完成JUnit测试环境的搭建。
(4)创建一个名为Count的Java类,在该类中实现一个encrypt()方法,该方法用于将传递的整数进行简单的加密,并返回加密后的值。创建Count类的代码如下:
package com.yxq.tools;
public class Count {
public String encrypt(int input){
int temp=2*input+100;
String over="YXQ"+temp;
return over;
}
}
(5)测试Count类。右击Count.java类文件,在弹出的快捷菜单中选择“新建/JUnit测试用例”命令,在弹出的“JUnit测试用例”对话框中进行图1.46所示的设置。
(6)单击“下一步”按钮,在弹出的“测试方法”对话框中,选择要测试的类中的方法,如图1.47所示。

图1.46 新建JUnit测试用例 图1.47 选择测试方法
(7)单击“完成”按钮,完成测试类CountTest的创建。最终CountTest类的代码如下:
package com.yxq.tools;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class CountTest {
@Before
public void setUp() throws Exception { //初始化方法,执行CountTest类时,先来执行该方法
}
@After
public void tearDown() throws Exception { //清理方法,测试结束后执行该方法
}
@Test
public void testEncrypt() { //在被测试的方法名前自动加入test并使方法名的第一个字母大写
fail("尚未实现");
}
}
(8)对CountTest类进行如下编码:
private Count count;
@Before
public void setUp() throws Exception {
count=new Count(); //创建Count类对象
}
@After
public void tearDown() throws Exception {
count=null; //销毁count对象
}
@Test
public final void testEncrypt() { //测试将整数10进行加密后的结果是否为YXQ120
assertEquals("测试testEncrypt()方法失败!",count.encrypt(10),"YXQ120");
}
上述代码中的assertEquals()方法是org.junit.Assert类中的静态方法。其用法如下:
assertEquals(String message,String expected,String actual)
其中,参数message表示断言失败输出的信息,该参数可以省略;expected表示期望的数据;actual表示实际的数据。assertEquals()方法用来断言expected表示的数据与actual表示的数据相等,若不等,则抛出异常并输出message表示的提示信息。
在Assert类中,常见的assertXxx()方法如表1.13所示。
表1.13 Assert类中常用assertXxx()方法
|
方 法 |
功 能 描 述 |
|
assertEquals(type expected,type actual) |
断言两个对象相等,其中 type表示数据类型,如基本数据类型、数组、Object类 |
|
assertNull(Object object) |
断言对象为NULL |
|
assertNotNull(Object object) |
断言对象不为NULL |
|
assertSame(Object expected,Object actual) |
断言两个引用变量引用的是同一个对象 |
|
续表 |
|
|
方 法 |
功 能 描 述 |
|
assertNotSame(Object expected,Object actual) |
断言两个引用变量引用的不是同一个对象 |
|
assertTrue(boolean condition) |
断言指定的条件为True |
|
assertFalse(boolean condition) |
断言指定的条件为False |
|
fail(String message) |
中断测试,并输出message表示的信息 |
(9)运行测试。单击Eclipse菜单栏中的
按钮,在弹出的菜单中选择“运行方式/JUnit测试”命令运行测试,若显示图1.43所示的运行结果,则说明Count类中的encrypt()方法正确;否则,则说明encrypt()方法中存在错误或方法实现的功能与事先预设计的不同。





