本系列教程将详细介绍Struts x的基本原理和使用方法读者可以参阅《Struts 系列教程》来比较Struts x和Struts x的相同点和不同点
在这篇文章中将以一个简单的例子(mystruts)来演示如何使用MyEclipse来开发运行Struts程序并给出了解决ActionForm出现乱码问题的方法读者可以从本文中了解开发Struts x程序的基本过程
一本文给出的程序要实现什么功能
mystruts是一个录入和查询产品信息的程序为了方便起见本例中的产品信息表只包括了产品ID产品名称和产品价格三个字段mystruts的主要功能如下
接受用户输入的产品ID产品名称和产品价格
验证这些字段的合法性如果某些字段的输入不合法(如未输入产品ID)程序会forward到一个信息显示页并显示出错原因
如果用户输入了正确的字段值程序会将这些字段值保存到数据库中并显示保存成功信息
用户输入产品名称并根据产品名称进行模糊查询如果存在符合要求的产品信息程序会以表格形式显示这些产品的信息同时显示记录数如果未查到任何记录会显示没有符合要求的记录!信息
二编写程序前的准备工作
建立数据库
在编写程序之前需要建立一个数据库(struts)和一个表(t_products)建立数据库和表的SQL脚本如下所示
#建立数据库struts
CREATEDATABASEIFNOTEXISTSstrutsDEFAULTCHARACTERSETGBK;
#建立表t_products
CREATETABLEIFNOTEXISTSstrutst_products(
product_idvarchar()NOTNULL
product_namevarchar()NOTNULL
pricefloatNOTNULL
PRIMARYKEY(product_id)
)ENGINE=InnoDBDEFAULTCHARSET=gbk;
建立一个支持strutsx的samples工程
用MyEclipse建立一个samples工程(Web工程)现在这个samples工程还不支持Strutsx(没有引入相应的Struts jar包strutsconfigxml文件以及其他和Struts相关的配置)然而在MyEclipse中这一切并不需要我们手工去加入而只需要使用MyEclipse的「New Struts Capabilities」对话框就可以自动完成这些工作
首先选中samples工程然后在右键菜单中选择「MyEclipse」 > 「New Struts Capabilities」启动「New Struts Capabilities」对话框对默认的设置需要进行如下的改动
()将Struts specification改为Struts
()将Base package for new classes改为struts
()将Default application resources改为strutsApplicationResources
改完后的「New Struts Capabilities」对话框如图所示
在设置完后点击Finish按钮关闭对话框在向samples工程添加支持Struts的功能后主要对samples工程进行了三个操作
()引入了Struts 的jar包(在samples的工程树中多了一个Struts Libraries节点)
()在WEBINF目录中添加了一个strutsconfigxml文件文件的默认内容如下面的代码所示
<?xmlversion=encoding=UTF?>
<!DOCTYPEstrutsconfigPUBLIC//ApacheSoftwareFoundation//DTDStrutsConfiguration//EN
config__dtd>
<strutsconfig>
<datasources/>
<formbeans/>
<globalexceptions/>
<globalforwards/>
<actionmappings/>
<messageresourcesparameter=strutsApplicationResources/>
</strutsconfig>
()在WEBINF中的webxml文件中添加了处理Struts动作的ActionServlet的配置代码如下
<servlet>
<servletname>action</servletname>
<servletclass>orgapachestrutsactionActionServlet</servletclass>
<initparam>
<paramname>config</paramname>
<paramvalue>/WEBINF/strutsconfigxml</paramvalue>
</initparam>
<initparam>
<paramname>debug</paramname>
<paramvalue></paramvalue>
</initparam>
<initparam>
<paramname>detail</paramname>
<paramvalue></paramvalue>
</initparam>
<loadonstartup></loadonstartup>
</servlet>
<servletmapping>
<servletname>action</servletname>
<urlpattern>*do</urlpattern>
</servletmapping>
到目前为止samples工程已经完全支持Struts了读者可以看到如果不使用MyEclipse那么上面所列出的配置文件的内容都必须手工输入因此使用MyEclipse来开发Struts程序可以省去很多配置xml文件的工作
三实现程序的首页(indexjsp)
首先在<samples工程目录>中建立一个mystruts目录然后在<samples工程目录> mystruts目录中建立一个indexjsp文件这个文件的内容如下
<%@pagepageEncoding=GBK%>
<%引用Strutstag%>
<%@tagliburi=htmlprefix=html%>
<html>
<head>
<title>主界面</title>
</head>
<body>
<tablealign=centercellpadding=width=%>
<tr>
<tdalign=rightwidth=%>
<%使用Strutstag%>
<html:linkforward=newProduct>录入产品信息</html:link>
</td>
<td>
<html:linkforward=searchProduct>查询产品信息</html:link>
</td>
</tr>
</table>
</body>
</html>
在MyEclipse中启动Tomcat(如果Tomcat处于启动状态在修改完配置文件后建议在MyEclipse的Servers页重新发布samples工程以使修改生效)在IE中输入如下的URL
我们发现在输入上面的URL后在IE中并未显示正确的运行结果而是抛出了如下的异常
MalformedURLException Cannot retrieve ActionForward named newProduct
这个异常表明程序并未找到一个叫newProduct的forward(forward将在后面详细地讲述)因此可以断定在JSP中使用forward时这个forward必须存在下面我们来添加indexjsp页面中所使用的两个forwardnewProduct和searchProduct这两个forward分别引向了建立产品信息的页面(newProductjsp)和查询产品信息的页面(searchProductjsp)我们可以在strutsconfigxml文件中<strutsconfig>节点中添加两个全局的forward代码如下
<globalforwards>
<forwardname=newProductpath=/mystruts/newProductjsp/>
<forwardname=searchProductpath=/mystruts/searchProductjsp/>
</globalforwards>
上面的代码中所示的newProductjsp和searchProductjsp目前并不存在(将在以后实现这两个JSP页面)现在重新输入上述的URL会得到如图所示的效果
图
如果想让indexjsp成为默认的JSP页面可以在webxml中的<welcomefilelist>节点中加入如下的内容
<welcomefile>indexjsp</welcomefile>
这时在IE中只要输入如下的URL就可以访问indexjsp页面了
//localhost/samples/mystruts
四实现添加和查询产品信息页面
在本节中主要实现了用于输入产品信息(newProductjsp)和查询产品信息(searchProductjsp)的JSP页面
在newProductjsp页面中有一个form在form中含有三个文本框用于分别输入产品ID产品名称和产品价格
在<samples工程目录>mystruts目录中建立一个newProductjsp文件代码如下
<%@pagepageEncoding=GBK%>
<%@tagliburi=htmlprefix=html%>
<html>
<head>
<title>录入产品信息</title>
</head>
<body>
<%向saveProduct动作提交产品信息%>
<html:formaction=saveProduct>
<tablewidth=%>
<tr>
<tdalign=center>
产品编号
<html:textproperty=productIDmaxlength=/>
<p>
产品名称
<html:textproperty=productName/>
<p>
产品价格
<html:textproperty=price/>
</td>
</tr>
<tr>
<tdalign=center>
<br>
<html:submitvalue=保存/>
</td>
</tr>
</table>
</html:form>
</body>
</html>
在searchProductjsp页面中有一个form为了方便起见在form中只提供了一个文本框用于对产品名称进行模糊查询在<samples工程目录> mystruts目录中建立一个searchProductjsp文件代码如下
<%@pagepageEncoding=GBK%>
<%@tagliburi=htmlprefix=html%>
<html>
<head>
<title>查询产品信息</title>
</head>
<body>
<%向searchProduct动作提交查询请求%>
<html:formaction=searchProduct>
<tablewidth=%>
<tr>
<tdalign=center>
产品名称
<html:textproperty=productName/>
</td>
</tr>
<tr>
<tdalign=center>
<br>
<html:submitvalue=查询/>
</td>
</tr>
</table>
</html:form>
</body>
</html>
现在启动Tomcat并使用如下两个URL来访问newProductjsp和searchProductjsp
在IE中输入上面的两个URL后并不能显示出相应的界面而会抛出JspException异常表明未找到saveProduct和searchProduct动作从这一点可以看出如果在JSP中使用Struts Action这些Action必须事先在strutsconfigxml文件中定义否则JSP程序就无法正常访问在这两个页面所使用的动作(saveProduct和searchProduct)将会在下面的部分介绍
五通过模型类操作数据库
在这一节我们来编写用于操作数据库的模型类由于本例子是Web程序因此建议在连接数据库时使用数据库连接池在<Tomcat安装目录>confCatalinalocalhost目录中打开samplesxml文件(如果没有该文件则建立一个samplesxml文件)在<Context>节点中加入如下的内容
配置连接池(用于连接数据库struts)
<Resourcename=jdbc/strutsauth=Container
type=javaxsqlDataSource
driverClassName=commysqljdbcDriver
url=jdbc:mysql://localhost:/struts?characterEncoding=GBK
username=root
password=
maxActive=
maxIdle=
maxWait=/>
本例中提供了两个可以操作数据库的模型类Product和SearchProduct其中Product用于验证由客户端提交的产品信息并向t_products表中写入这些信息而SearchProduct类用于对t_products表的product_name字段进行模糊查询并返回查询到的产品信息(包括产品ID产品名称和产品价格)
由于Product和SearchProduct都需要使用数据库连接池来连接数据库因此可以将连接数据库的工作提出来作为一个父类(Struts类)提供代码如下
packageutil;
importjavasqlConnection;
publicclassStruts
{
protectedjavaxnamingContextctx=newjavaxnamingInitialContext();
protectedjavaxsqlDataSourceds;
protectedConnectionconn;
publicStruts()throwsException
{
ds=(javaxsqlDataSource)ctxlookup(java:/comp/env/jdbc/struts);
conn=dsgetConnection();//从数据库连接池获得一个Connection
}
}
在<samples工程目录>src目录中建立一个Productjava文件代码所示
packagemystrutsmodel;
importjavasql*;
importmystrutsactionform*;
publicclassProductextendsutilStruts
{
privateProductFormform;
publicProduct(ProductFormform)throwsException
{
super();
thisform=form;
validate();
}
//验证客户端提交的数据
publicvoidvalidate()throwsException
{
if(formgetProductID()trim()equals())
thrownewException(产品ID不能为空!);
if(formgetProductID()length()>)
thrownewException(产品ID最长为位!);
if(formgetProductName()trim()equals())
thrownewException(产品名称不能为空);
if(pare(formgetPrice())<=)
thrownewException(产品价格必须大于);
}
//将客户端提交的产品信息保存到t_products中
publicvoidsave()throwsException
{
try
{
StringproductID=formgetProductID();
StringproductName=formgetProductName();
floatprice=formgetPrice();
Stringsql=INSERTINTOt_productsVALUES(+productID+
++productName++StringvalueOf(price)+);
PreparedStatementpstmt=connprepareStatement(sql);
pstmtexecuteUpdate();//执行INSERT语句
pstmtclose();
connclose();
}
catch(Exceptione)
{
thrownewException(egetMessage());
}
}
}
在Product类中使用了一个ProductForm类这个类是一个ActionForm类它的功能是保存客户端提交的数据关于这个类将在下面详细介绍Product类通过构造方法的form参数将客户端提交的数据传入Product类的对象实例中并在构造方法中验证这些数据如果发现数据不合法就会抛出一个异常当客户端提交的数据合法后成功建立了一个Product类的对象实例然后可以通过简单地调用save方法将数据保存到t_products表中
与Product类似在<samples工程目录>src目录中建立一个SearchProductjava文件代码如下
packagemystrutsmodel;
importjavasql*;
importjavautil*;
importmystrutsactionform*;
publicclassSearchProductextendsutilStruts
{
privateProductFormform;
publicSearchProduct(ProductFormform)throwsException
{
super();
thisform=form;
}
//查询产品信息并通过List返回查询结果
publicList<String[]>search()throwsException
{
List<String[]>result=newLinkedList<String[]>();
Stringsql=SELECT*FROMt_productsWHEREproduct_namelike%
+formgetProductName()+%;
PreparedStatementpstmt=connprepareStatement(sql);
ResultSetrs=pstmtexecuteQuery();//开始执行SELECT语句
while(rsnext())
{
String[]row=newString[];
row[]=rsgetString();
row[]=rsgetString();
row[]=rsgetString();
resultadd(row);
}
rsclose();
connclose();
returnresult;
}
}
在SearchProduct类也使用了ProductForm类但在SearchProduct中并不会验证ProductForm对象实例中的数据而只是将ProductForm对象作为传递查询请求信息(实际上只需要产品名称)的工具而已
六实现控制器
在这一节要实现的控制器是基于Struts的Web程序的核心部分之一控制器实质上也是普通的Java类但这个Java类一般要从orgapachestrutsactionAction类继承控制器的主要功能是接受并处理从JSP页面提交的数据通过模型(Model)和数据库交互以及forward到相应的页面(可以是任何页面如htmlJSP和Servlet等)在实现控制器之前需要先实现一个ActionForm类 这个类的作用是保存JSP页面提交的数据在<samples工程目录>src目录中建立一个ProductFormjava文件代码如下
packagemystrutsactionform;
importorgapachestrutsaction*;
publicclassProductFormextendsActionForm
{
privateStringproductID;//产品ID
privateStringproductName;//产品名称
privatefloatprice;//产品价格
publicStringgetProductID()
{
returnproductID;
}
publicvoidsetProductID(StringproductID)
{
thisproductID=productID;
}
publicStringgetProductName()
{
returnproductName;
}
publicvoidsetProductName(StringproductName)
{
thisproductName=productName;
}
publicfloatgetPrice()
{
returnprice;
}
publicvoidsetPrice(floatprice)
{
thisprice=price;
}
}
从上面的代码可以看出ActionForm类一般从orgapachestrutsactionActionForm类继承而且在类中需要按着需要保存的数据表字段添加属性如产品ID的属性是productName在MyEclipse中可以只定义三个private变量然后使用MyEclipse的「Source」 > 「Generate Getters and Setters……」功能自动产生getter和setter方法但在给这些属性取名时要注意private变量的名子和数据表的字段名没有直接的关系但必须和JSP页面中的<html>标签的property属性值一致如<htmltext property=productName />表示输入产品名称的文本框其中property属性的值就是ProductForm类中的productName变量如果不一致将会抛出异常其他和ProductForm类的属性对应的<html>标签可以查看上面的代码
光有ActionForm类还不够还需要在strutsconfigxml中的<strutsconfig>节点中添加如下的内容
<formbeans>
<formbeanname=saveProductFormtype=mystrutsactionformProductForm/>
<formbeanname=searchProductFormtype=mystrutsactionformProductForm/>
</formbeans>
上面的代码所配置的两个ActionForm实际上指的是同一个ProductForm类但这个ProductForm类在后面要讲的两个动作里都要使用为了更容易理解为这个ProductForm起了两个不同的别名(saveProductForm和searchProductForm)
下面来实现saveProduct动作的代码Struts Action类必须一般从orgapachestrutsactionAction类继承一般在Struts Action类需要覆盖Action类的execute方法这个方法有每次客户端访问Struts Action时调用我们可以在方法中处理客户端提交的数据访问数据库等工作这个方法返回一个ActionForward类型的值表明在执行完execute后要forward到的页面描述saveProduct动作的类叫SaveProductAction代码如下
packagemystrutsaction;
importjavaxservlethttp*;
importorgapachestrutsaction*;
importmystrutsactionform*;
importmystrutsmodel*;
publicclassSaveProductActionextendsAction
{
//在客户端访问saveProduct动作时执行该方法
publicActionForwardexecute(ActionMappingmappingActionFormform
HttpServletRequestrequestHttpServletResponseresponse)
{
ProductFormsaveProductForm=(ProductForm)form;
try
{
Productproduct=newProduct(saveProductForm);
productsave();//保存产品信息
requestsetAttribute(info保存成功!);
}
catch(Exceptione)
{
requestsetAttribute(infoegetMessage());
}
returnmappingfindForward(save);
}
}
在SaveProductAction类中使用了模型类Product验证并保存产品信息并将操作结果信息保存在request的属性中key为info在execute的最后使用了ActionMapping类的findForward方法在strutsconfigxml中寻找一个叫save的forward这个forward是一个JSP页用于显示是否将产品信息保存成功的信息为了可以在strutsconfigxml中查找这个forward需要在strutsconfigxml的<actionmappings>节点中加入如下的内容
<actionname=saveProductFormpath=/saveProductscope=requesttype=mystrutsactionSaveProductAction>
<forwardname=savepath=/mystruts/savejsp/>
</action>
从上面的代码可以看出那个用于显示保存状态信息的JSP页面叫savejsp在<samples工程目录>mystruts目录中建立一个savejsp文件代码如下
<%@pagepageEncoding=GBK%>
${}
在IE中输入如下的URL
在文本框中输入相应的信息后点保存按钮如果输入的数据是合法的就会将数据保存在t_products中否则会显示出错的原因 searchProduct动作的实现和saveProduct差不多也会为三步实现动作类(SearchProductAction)在strutsconfigxml中添加配置信息和实现用于显示查询结果的JSP文件下面的代码分别显示了这三步所要编写的代码
SearchProductActionjava
packagemystrutsaction;
importjavaxservlethttp*;
importorgapachestrutsaction*;
importmystrutsactionform*;
importmystrutsmodel*;
importjavautil*;
publicclassSearchProductActionextendsAction
{
publicActionForwardexecute(ActionMappingmappingActionFormform
HttpServletRequestrequestHttpServletResponseresponse)
{
ProductFormsearchProductForm=(ProductForm)form;
try
{
SearchProductsearchProduct=newSearchProduct(searchProductForm);
List<String[]>result=searchProductsearch();//查询产品信息
if(resultsize()>)//有符合条件的产品信息
{
requestsetAttribute(resultresult);
requestsetAttribute(info记录数+StringvalueOf(resultsize()));
}
else//没有查到任何产品信息
requestsetAttribute(info没有符合要求的记录!);
}
catch(Exceptione)
{
requestsetAttribute(infoegetMessage());
}
returnmappingfindForward(search);
}
}
在strutsconfigxml中配置searchProduct动作
<actionname=searchProductFormpath=/searchProductscope=request type=mystrutsactionSearchProductAction>
<forwardname=searchpath=/mystruts/searchjsp/>
</action>
searchjsp
<%@pagepageEncoding=GBK%>
<%@tagliburi=logicprefix=logic%>
<%@tagliburi=prefix=c%>
<html>
<body>
<%从request的result中取出查询结果%>
<c:setvar=resultvalue=${requestScoperesult}/>
<tablewidth=%>
<tralign=center>
<td>
${}
</td>
</tr>
<tralign=center>
<td>
<logic:presentname=result>
<tableborder=>
<tralign=center>
<td>产品ID</td>
<td>产品名称</td>
<td>价格</td>
</tr>
<logic:iterateid=rowname=result>
<tr><td>${row[]}</td>
<td>${row[]}</td>
<td>${row[]}</td>
</tr>
</logic:iterate>
</table>
</logic:present>
</td>
</tr>
</table>
</body>
</html>
在IE中输入如下的URL
//localhost/samples/%mystruts/searchProductjsp
在产品名称文本框中输入产品名称的一部分程序就会查询出所有包含输入的产品名称的产品信息并将结果显示出来
七解决ActionForm的乱码问题
到现在为止程序的功能部分已经全部实现完了但还存在一个问题当我们在产品名称中输入中文时虽然将客户端提交的数据成功保存到数据库中但是在t_products表中的product_name字段显示的都是乱码产生这个问题的原因只有一个就是客户端提交的数据的编码格式和数据库的编码格式不一致造成的当然解决这个问题的方法有很多但笔者认为最容易的就是使用过滤器所谓过滤器就是在客户端提交数据后在交由服务端处理之前所执行的一段服务端代码(一般为Java代码)一个过滤器是一个实现javaxservletFilter接口的类在本例中要使用的过滤器类叫EncodingFilter实现代码如下
EncodingFilterjava
packagefilter;
importjavaioIOException;
importjavaxservlet*;
publicclassEncodingFilterimplementsFilter
{
publicvoiddestroy(){}
publicvoiddoFilter(ServletRequestrequestServletResponseresponse
FilterChainchain)throwsIOExceptionServletException
{
requestsetCharacterEncoding(GBK);//将客户端提交的数据设为GBK编码格式
//继续处理客户端提交的数据如果不写这条语句Servlet引擎将不会处理所过滤的页面
chaindoFilter(requestresponse);
}
publicvoidinit(FilterConfigfilterConfig)throwsServletException{}
}
Filter接口的doFilter方法是过滤器的核心方法其中FilterChain类的doFilter方法允许继续处理客户端提交的数据我们还可以使用这个方法来临时关闭Web站点的某个或全部的页面(根据过滤器的设置而定)由于本书的数据库使用的是GBK编码格式因此需要使用ServletRequest的setCharacterEncoding方法将客户端提交的数据也设为GBK编码格式
除了实现过滤器类我们还需要在webxml中的<webapp>节点加入如下的配置信息才能使过滤器生效
在webxml中配置过滤器
<filter>
<filtername>EncodingFilter</filtername>
<filterclass>
filterEncodingFilter
</filterclass>
</filter>
<filtermapping>
<filtername>EncodingFilter</filtername>
<urlpattern>/*</urlpattern>
</filtermapping>
在重新启动Tomcat后重新输入一条带中文的产品信息看看是否可以将中文保存在数据库中?