Struts 1高级应用

来源:互联网 发布:资管新规 知乎 编辑:程序博客网 时间:2024/05/23 17:12

学习内容

 DispatchAction

 类型转换器

 Struts 1异常处理

能力目标

 能熟练使用DispatchAction简化开发

 能根据具体情况使用国际化和异常处理

 


本章简介

在前面两章中,我们学习了Struts 1的基础知识,包括Struts 1的运行原理、体系结构和标签库等。Struts作为经典的MVC框架给开发人员带来了更加简单和高效的开发模式,并且增强了应用程序的健壮性、重用性和扩展性。

在应用开发工作中中,我们会遇到一些复杂的业务,这时如果只使用前面的内容将很难解决问题或着解决起来相当麻烦。本章将学习一些Struts 1的高级内容, DispatchAction、国际化等,通过这些内容我们可以简化对复杂业务的处理。

核心技能部分

2.1 DispatchAction

在第一章,我们使用Struts实现了对管理员的增、删、改、查等操作。在实现过程中,我们使用了多个Action处理不同的业务,例如LoginAction处理登录业务,AddAction处理添加管理员业务,DelAction处理删除管理员业务。在struts-config.xml中需要对这些Action分别进行配置。一个Action处理一个业务虽然可行,但是配置繁琐,代码重复,不利于维护和扩展,为了改变这种情况,可以使用DispatchAction类。

DispatchAction类是Action的子类,在该类中我们无需重写execute方法,而是可以自定义多个业务处理方法,这就避免了每个业务都需要一个Action的情况。

下面我们通过一个案例来演示DispatchAction的使用和配置。图2.1.1是我们第二阶段学习时用过的图书表,现在针对这个表实现查询功能,包括按书名查询、按作者查询和按出版社名查询。

 

图2.1.1 book表

(1) 创建对应图书表的实体Bean(Book),代码如示例2.1所示。

示例2.1

public class Book {private String ISBN;private String name;private String author;private String publish;private double price;private int currcount;public Book() {}public Book(String iSBN, String name, String author, String publish,double price, int currcount) {ISBN = iSBN;this.name = name;this.author = author;this.publish = publish;this.price = price;this.currcount = currcount;}//省略getter/setter方法}


(2) 实现数据库连接和关闭的工厂类DaoFactory不再给出参考代码,下面是使用JDBC实现查询功能的JavaBean(BookDao)。

示例2.2

public class BookDao {Connection conn = null;PreparedStatement stmt = null;ResultSet rs = null;public List<Book> getBooks(String colName,String keyWords) {  //查询图书List<Book> BookList=new ArrayList<Book>();try {conn=DaoFactory.getConn();stmt=conn.prepareStatement("select * from BOOK where "+colName+" like ?");stmt.setString(1, "%"+keyWords+"%");rs=stmt.executeQuery();while(rs.next()) {Book book=new Book();book.setISBN(rs.getString(1));book.setName(rs.getString(2));book.setAuthor(rs.getString(3));book.setPublish(rs.getString(4));book.setPrice(rs.getFloat(5));book.setCurrcount(rs.getInt(6));BookList.add(book);}} catch (Exception e) {e.printStackTrace();} finally{DaoFactory.closeAll(rs,stmt,conn);}return BookList;}}


我们需要实现三种查询:按书名查询、按作者查询和按出版社查询。不管哪种查询,在使用JDBC实现时都会出现很多重复代码,因为变化的只有列名和用户输入关键字。为了避免代码重复,我们在BookDao类中定义了一个带参的方法getBooks(String colName, String keyWords),第一个参数表示列名,第二个参数表示用户输入的关键字。

(3) 创建查询视图页面(query.html),该页面主要包含一个表单,如图2.1.2所示。

 

图2.1.2 查询页面

参考代码如示例2.3所示。代码比较简单,但是请大家注意其中的加粗部分,这些代码会在以后的内容中合适的位置讲解。

示例2.3

<form action="query.do" method="post"><table width="330" height="94" border="0" align="center"><tr><td align="center">关键字:<input type="text" name="keywords"></td></tr><tr><td align="center"><input name="rd" type="radio" value="queryByName" checked>按书名查询<input name="rd" type="radio" value="queryByAuthor">按作者查询<input name="rd" type="radio" value="queryByPublisher">按出版社查询</td></tr><tr><td align="center"><input type="submit" value="查询"></td></tr></table></form>


(4) 下面是与表单对应的ActionForm(BookForm),代码比较简单,此处不再多述。

 

示例3.4

public class BookForm extends ActionForm {private String keywords;private String rd;public String getKeywords() {return keywords;}public void setKeywords(String keywords) throwsUnsupportedEncodingException{     //为了避免出现中文乱码,我们对用户输入的关键字进行了转码处理this.keywords = new String(keywords.getBytes("iso-8859-1"),"utf-8");}public String getRd() {return rd;}public void setRd(String rd) {this.rd = rd;}}


(5) 查询结果会在视图list.jsp中显示,图2.1.3是按“工业”出版社进行查询的结果。

图2.1.3 查询结果


参考代码如下所示,代码比较简单,此处不再多述。

示例2.5

<body><table border="1" align="center"><caption>图书列表</caption><tr bgcolor="darkgray"><td align="center">ISBN</td><td align="center">图书名称</td><td align="center">图书作者</td><td align="center">出版社</td><td align="center">售价</td><td align="center">库存</td></tr><%List<Book> bookList=(List<Book>)request.getAttribute("bookList");for(int j=0;j<bookList.size();j++){Book book=bookList.get(j); %><tr><td align="center"><%=book.getISBN() %></td><td align="center"><%=book.getName() %></td><td align="center"><%=book.getAuthor() %></td><td align="center"><%=book.getPublish() %></td><td align="center"><%=book.getPrice() %></td><td align="center"><%=book.getCurrcount() %></td></tr><%} %></table></body>


(6) 下面就该创建业务逻辑控制器Action了,我们可以像第一章那样:就是一个Action对应一个查询业务,这就需要创建三个Action,虽然能够实现功能,但是会出现代码重复、配置繁琐的问题,这里我们使用DispatchAction,代码如下所示。

示例2.6

public class BookAction extends DispatchAction {  //继承DispatchAction类BookDao bd=new BookDao();List<Book> bookList=new ArrayList<Book>();//按书名查询public ActionForward queryByName(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {BookForm bf=(BookForm)form;bookList=bd.getBooks("NAME",bf.getKeywords());request.setAttribute("bookList", bookList);return mapping.findForward("list");}//按作者查询public ActionForward queryByAuthor(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {BookForm bf=(BookForm)form;bookList=bd.getBooks("AUTHOR",bf.getKeywords());request.setAttribute("bookList", bookList);return mapping.findForward("list");}//按出版社查询public ActionForward queryByPublisher(ActionMapping mapping, ActionFormform,HttpServletRequest request, HttpServletResponse response) {BookForm bf=(BookForm)form;bookList=bd.getBooks("PUBLISH",bf.getKeywords());request.setAttribute("bookList", bookList);return mapping.findForward("list");}}


上述代码创建了一个业务逻辑控制器BookAction类,该类继承了DispatchAction。在该类中我们并没有像第一章那样重写execute方法,而是自定义了三个方法分别实现三种查询。

注意:

如果某个类继承了DispatchAction,那么这个类中自定义的方法必须和execute有一样的方法签名,即它的返回值和参数列表必须和execute方法一样。

(7) 最后在struts-config.xml中进行相关配置,代码如下所示。

示例2.7

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd"><struts-config><form-beans><form-bean name="BookForm" type="com.yourcompany.struts.BookForm"/></form-beans><action-mappings><action      name="BookForm"parameter="rd"   //参数属性      path="/query"      scope="request"      type="com.yourcompany.struts.BookAction"><forwardname="list"path="/list.jsp"/></action></action-mappings></struts-config>


上述代码与第一章唯一不同的地方就是加粗部分,这是使用DispatchAction的关键配置。在本案例中,所有业务都是由一个Action处理的,或者说每个业务对应Action中的一个方法,那么Struts是怎么分清不同的业务将来由Action中的哪个方法来处理呢?这就需要<action>元素的parameter属性进行设置。在本案例中,parameter属性的值是“rd”,而“rd”是表单中某个元素(单选按钮)的名字,同时这些表单元素(单选按钮)value属性的值正好是BookAction中定义的方法名。弄清楚这些配置后,我们看一下具体使用流程:

(1) 用户打开表单所在的页面query.html,输入关键字并选择一个单选按钮,假设选择的是【按书名查询】,该单选按钮的名字是rd,值是queryByName。

(2) 用户提交表单时,用户输入的关键字和选中的单选按钮的“名值对”信息被一起发送到了服务器端。

(3) 在struts-config.xml文件中,<action>元素的parameter属性的值是rd,即接收表单中名字是rd元素的值,而这个值是BookAction中一个方法的名字。至此Struts就知道该调用哪个方法来处理业务了。

 一般来说,DispatchAction通常适合多个业务公用一个表单的业务场合。

 

2.2 使用实体对象简化ActionForm

在进行Struts开发时,我们经常需要编写ActionForm或者配置动态Form来封装表单数据,但是当数据库表增减字段、业务逻辑发生变化时,我们需要修改相关的实体类、ActionForm和struts-cofig.xml文件,维护工作十分繁琐。

根据以往的经验我们发现:ActionForm和实体类通常非常的相似。例如,在第一章的登录案例中,实体类和ActionForm的代码如示例2.8所示。

示例2.8

public class Admin {private int id;       //登录IDprivate String name;  //登录名称private String pwd;   //登录密码     //省略getter和setter方法}public class LoginForm extends ActionForm{private String logname;//登录名称private String logpwd;//登录密码//省略getter和setter方法}
上述两个类几乎一样,大部分代码都是重复的,所以我们考虑可以通过实体类来简化ActionForm,即把实体类作为ActionForm的属性来使用。下面我们对第一章的登录案例进行优化,具体步骤如下:

(1) 修改实体类Admin,使其中的属性名和表单元素的名字保持一致,代码如下所示。

示例2.9

public class Admin {private int id;           //登录IDprivate String logname;  //登录名称private String logpwd;   //登录密码public Admin() {}public Admin(String logname, String logpwd) {this.logname = logname;this.logpwd = logpwd;}     //省略getter和setter方法}
(2) 修改ActionForm,把实体类作为属性使用,代码如下所示。

示例2.10

public class LoginForm extends ActionForm{private Admin admin=new Admin();   //使用实体类作为属性public Admin getAdmin() {return admin;}public void setAdmin(Admin admin) {this.admin = admin;}}
(3) 修改视图login.jsp,代码如下所示。

示例2.11

<form action="login.do" method="post" name="loginForm"><table width="397" height="169" border="0" align="center"><tr><td colspan="2" align="center">管理员登录</td></tr><tr><td width="163" align="right">登录名称:</td><td width="218"><input name="admin.logname" type="text" class="txt"></td></tr><tr><td align="right">登录密码:</td><td><input name="admin.logpwd" type="password" class="txt"></td></tr><tr><td colspan="2" align="center"><input type="submit" name="Submit" value="提交"></td></tr></table></form>
在上述代码中,一定要注意加粗显示的表单元素的名字,这时的命名方式跟以前是不一样的。表单元素name属性的值由两部分组成,中间用“.”隔开,前半部分必须与LoginForm中的属性名保持一致,后半部分必须与实体类中的某个属性保持一致。

(4) 修改业务逻辑控制器LoginAction,代码如下所示。

示例2.12

public class LoginAction extends Action {public ActionForward execute(ActionMapping mapping,ActionFormform,HttpServletRequest req,HttpServletResponse res){LoginForm lf=(LoginForm)form;String logname=lf.getAdmin().getLogname();String logpwd=lf.getAdmin().getLogpwd();AdminDao ad=new AdminDao();if(ad.checkLogin(logname, logpwd)){req.setAttribute("admin", new Admin(logname,logpwd));return mapping.findForward("success");}elsereturn mapping.findForward("fail");}}
注意上面加粗部分的代码,我们首先使用getAdmin()方法获得Admin实例对象,然后调用该对象中的getter方法获得属性的值。

注意:

在使用实体类作为ActionForm的属性时,必须在ActionForm中实例化实体类,参看示例2.10中的粗体代码。

2.3 Struts 1异常处理

当我们使用Struts框架进行Web开发时,如果程序发生异常,那么这些异常的堆栈信息有可能会显示到页面上,这只会让用户困惑不解,这种情况显然对用户十分的不友好。在Struts1中对异常的处理其实比较简单,当程序发生异常时,我们只需要跳转到指定的错误提示页面即可。接下来我们就使用Struts1处理异常,具体步骤如下:

(1) 在异常可能发生的位置使用throws关键字声明异常

public class AddForm extends ActionForm {private Date birthday;private int weight;private int height;private String name; public Date getBirthday() {return birthday;}//声明异常public void setBirthday(Date birthday) throws Exception{this.birthday = birthday;}//省略部分代码}


ActionForm使用setter方法封装表单中的数据,所以setBirthday方法会引发异常,我们就给这个方法声明异常。

(2) 在struts-config.xml中配置全局异常处理元素

<global-exceptions><exception key="" type="java.lang.IllegalArgumentException"path="/error.jsp"/><exception key="" type="javax.servlet.ServletException"path="/error.jsp"/></global-exceptions>
<global-exceptions>元素用来配置全局异常,子元素<exception>用来配置具体的异常处理,一个<exception>对应一个异常处理。表2-1-1列出了该元素的常用属性。

表2-1-1 <exception>元素的常用属性

属性名

属性说明

key

从资源文件中获取错误提示消息,没有使用资源文件的话赋一个空字符串即可。

type

指定要处理的异常类。

path

指定出现异常时跳转到哪个错误提示页面。

(3) 创建错误提示页面error.jsp,代码如下所示。

<html:html><head><title>错误提示页面</title></head><body><center> 不好意思,程序暂时出现异常。</center></body></html:html>
在本例中,我们直接把错误提示消息放在error.jsp页面中。这时我们再次运行程序就不会出现将异常堆栈信息输出到客户端浏览器的情况了,而是跳转到了error.jsp页面中,如图2.1.4所示。

 

图2.1.4 异常处理

2.4 Struts 1国际化

国际化是指我们的Web应用程序在运行时根据客户端所在国家(地区)的不同而显示不同的语言界面。在程序中引入国际化的目的是为了给不同国家(地区)的用户提供自适应、更友好的界面。

国际化的英文单词是Internationalization,简称“I18N”,“I”和“N”是单词的首尾字母,18表示中间的字母数量。

图2.1.5和图2.1.6展示了某个软件的国际化应用。

 

图2.1.5 中文语言界面

 

图2.1.6 英文语言界面

Struts1提供了对国际化的支持,并且使用起来非常简单。

首先我们需要定义资源文件,通常需要两个,一个是中文资源文件,一个是英文资源文件。我们把页面上的所有文本信息以中英文两种形式分别保存到这两个资源文件中。页面上使用<bean:message>标签可以从资源文件中读取相应文本并显示到页面上。

例如,当程序运行环境是在中国时,Struts就通过<bean:message>标签读取中文资源文件,从而显示中文界面;当运行环境是在美国时,Struts就通过<bean:message>标签读取英文资源文件,从而显示英文界面。

下面我们就给前面添加学生的程序提供国际化,并以此介绍使用Struts 1国际化的实现步骤。

(1) 创建资源文件。

Struts默认创建的资源文件名是ApplicationResources.properties。我们自定义的资源文件可以这样命名:baseName_language.properties,其中baseName是资源文件的基本名,language表示所支持的语言,例如zh表示中文,en表示英文。本例中我们创建两个资源文件,分别是ApplicationResources_zh.properties和ApplicationResources_en.properties。

在MyEclipse中打开任意一个资源文件后,可以在【Properties】视图中通过配置完成资源文件的编写,如图2.1.7所示,也可以在【Source】视图中通过编码完成资源文件的编写。

 

图2.1.7配置资源文件

下面是ApplicationResources_zh.properties资源文件的源代码。

addstu.title=\u6DFB\u52A0\u5B66\u751Faddstu.name=\u5B66\u751F\u59D3\u540Daddstu.birthday=\u5B66\u751F\u751F\u65E5addstu.height=\u5B66\u751F\u8EAB\u9AD8addstu.weight=\u5B66\u751F\u4F53\u91CDaddstu.btn=\u6DFB\u52A0addstu.exception=\u4E0D\u597D\u610F\u601D\uFF0C\u7A0B\u5E8F\u6682\u65F6\u51FA\u73B0\u5F02\u5E38\u3002addstu.zh=\u4E2D\u6587addstu.en=\u82F1\u6587
我们发现源代码中出现了很多类似于乱码的东西,这是因为中文属于系统不能识别的非西欧文字,必须使用native2ascii命令进行转码,语法如下所示:

native2ascii源资源文件目标资源文件

转码后,中文就成了乱码,所以这是正常情况。

下面是ApplicationResources_en.properties资源文件的源代码。

addstu.title=add a new Studentaddstu.name=student'nameaddstu.birthday=student'birthdayaddstu.height=student'heightaddstu.weight=student'weightaddstu.btn=Addaddstu.exception=Sorry,Program temporarily appear unusual.addstu.zh=Chineseaddstu.en=EngLish
我们看到资源文件是以“键值对”的形式进行配置的,“键”和“值”都可以自定义,并且英文资源文件和中文资源文件的“键”必须相同。

注意

高版本的MyEclipse可以自动对中文资源文件进行转码,否则就只能使用native2ascii命令手工转码。

(2) 加载资源文件

资源文件通过struts-config.xml进行配置,默认从WEB-INF/classes路径加载,所以资源文件必须放在WEB-INF/classes路径或其子路径下。本例中的资源文件放在com.zy包中,代码如下所示:

<struts-config>

<!-- 其他代码省略 -->

<message-resources parameter="com.zy.ApplicationResources" />

</struts-config>

配置时,只需要指定资源文件的基本名即可。

(3) 在页面中使用<bean:message>标签显示国际化文本消息。代码如下所示。

<body><center><html:link href="changeLang.do?lang=zh"><bean:message key="addstu.zh"/></html:link> <html:link href="changeLang.do?lang=en"><bean:message key="addstu.en"/></html:link></center><html:form action="add.do" method="post"><table border="0" align="center"><caption><bean:message key="addstu.title"/></caption><tr><td><bean:message key="addstu.name"/>:</td><td><html:text property="name"/><html:errors property="checkName"/></td></tr><tr><td><bean:message key="addstu.birthday"/>:</td><td><html:text property="birthday"/></td></tr><tr><td><bean:message key="addstu.height"/>:</td><td><html:text property="height"/></td></tr><tr><td><bean:message key="addstu.weight"/>:</td><td><html:text property="weight"/></td></tr><tr><td colspan="2" align="center"><html:submit><bean:message key="addstu.btn"/></html:submit></td></tr></table></html:form></body>
<bean:message>元素中的key属性用来从资源文件中取值,所以该属性的值一定是资源文件中某个“键”的名字。

(4) 为了能够让用户自由选择界面语言,我们在上述代码中增加了两个超链接,如图2.1.8所示。

 

图2.1.8 中文界面

当单击【中文】超链接时,通过一个Action改变当前的语言环境为中文;当单击【英文】超链接时,通过同样的Action改变当前的语言环境为英文,该Action的代码如下所示。

public class LanguageAction extends Action {public ActionForward execute(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response){String lang=request.getParameter("lang");if(lang.equals("zh")){request.getSession().setAttribute(Globals.LOCALE_KEY, Locale.CHINESE);}if(lang.equals("en")){request.getSession().setAttribute(Globals.LOCALE_KEY,Locale.ENGLISH);}return mapping.findForward("addstu");}}
加粗部分就是用来改变当前语言环境的代码,具体是通过setAttribute方法来设置语言环境,该方法的第一个参数Globals.LOCALE_KEY表示当前语言环境的键,Struts通过这个键来确定当前的语言环境,第二个参数表示当前语言环境的值,Locale.CHINESE表示中文;Locale.ENGLISH表示英文。

当我们单击【英文】超链接时,整个页面就会显示英文消息文本,如图2.1.9所示。

 

图2.1.9 英文界面


本章总结

 DispatchAction类是Action的子类,在该类中我们无需重写execute方法,而是可以自定义多个业务处理方法,这就避免了每个业务都需要一个Action的情况。

(1)如果某个类继承了DispatchAction,那么这个类中自定义的方法必须和execute有一样的方法签名,即它的返回值和参数列表必须和execute方法一样。    

(2)需要对<action>元素的parameter属性进行设置。    

(3)访问Action的时候,按照约定传递parameter,说明要调用的方法名。

 可以使用实体对象简化ActionForm。

 Struts1的异常处理。

(1)在异常可能发生的位置使用throws关键字声明异常。

(2)在struts-config.xml中配置全局异常处理元素(3)创建错误提示页面。

 国际化是指我们的Web应用程序在运行时根据客户端所在国家(地区)的不同而显示不同的语言界面。在程序中引入国际化的目的是为了给不同国家(地区)的用户提供自适应、更友好的界面。

(1)创建资源文件。

(2)加载资源文件。

(3)在页面中使用<bean:message>标签显示国际化文本消息。


任务实训部分

1:使用DispatchAction重构管理员示例

训练技能点

Struts 1DispatchAction

需求说明

在第一章的任务实现部分,我们实现了对管理员进行增、删和查的功能。现在使用DispatchAction重构这一事例。

实现步骤

(1) 创建AdminAction 继承DispatchAction。

(2) 将原AddAction 、DelAction、QueryAction合并进AdminAction ,分别创建三个方法,addAdmin、delAdmin、queryAdmin。

(3) 配置AdminAction 添加parameter参数,并在访问action的时候传递该参数。


2:给管理员示例提供异常处理

训练技能点

Struts 1异常处理

需求说明

给上面的实训任务增加异常处理

实现步骤

(1)  在系统各处throws异常。

(2)  在struts-config.xml文件中配置异常处理。

(3)  开发异常信息页面。


3:给添加管理员示例提供国际化支持

训练技能点

Struts 1国际化

需求说明

给上面的实训任务增加中英文国际化支持。

实现步骤

(1 )  编写两个资源文件。

(2)  在struts-config.xml文件中加载资源文件。

(3)  在addAdmin.jsp页面中使用<bean:message>标签显示资源文件中的消息。

(4) 编写一个Action用来实现改变当前语言环境。

4:优化添加学生示例

训练技能点

使用实体对象简化ActionForm

需求说明

在本章的核心技能部分,我们讲解了一个贯穿示例“添加学生”。现在要求使用实体对象简化本示例中的ActionForm。

巩固练习

一、选择题

1. 以下关于DispatchAction的说法正确的是()。

A. Action继承了DispatchAction

B. 继承DispatchAction时必须重写execute方法

C. 继承DispatchAction时可以自定义方法,DispatchAction对这些方法没任何要求

D. DispatchAction是Action的子类

2. 使用DispatchAction必要的步骤是()。

A. 创建Action,并开发业务处理方法。

B. 设置<action>元素的parameter属性。

C. 访问Action的时候,按照约定传递parameter,说明要调用的方法名。

D. 必须为Action指定FormBean。s

3.以下关于Struts 1资源文件说法正确的是()。

A. 资源文件的名字可以自定义,没有任何规定

B. 资源文件只要放在正确的路径下,Struts就能找到了,无需在struts-config.xml中进行配置

C. 资源文件只能放在WEB-INF/classes的根目录下

D. 中文资源文件需要转码

4.在Struts1中使用()显示错误提示消息。

A. <html:errors>

B. <html:messages>

C. <html:error>

D. <html:message>

5.在Struts1中客户端国家语言信息被放置在()对象中。

A.  Application

B.  Session

C.  request

D.  page 

原创粉丝点击