Struts 2配置文件
Action的配置
Result的配置
属性驱动与模型驱动
能力目标
熟练进行Struts 2配置
熟练使用属性驱动和模型驱动
本章简介
上一章我们初步学习了Struts 2框架,包括Struts 2体系结构和运行流程,并通过登录案例介绍了使用Struts 2进行开发的基本步骤。
本章将深入学习Struts 2框架,主要内容是Struts 2的配置文件,包括Action的配置、Result的配置等等,只有掌握了配置文件,才能更好的使用和扩展Struts 2的功能。
核心技能部分
通过上一章的学习,我们知道Struts 2框架默认的配置文件是struts.xml,该文件通常放在WEB-INF\classes目录下,该目录下的struts.xml会自动被Struts 2框架加载。本节将详细介绍该配置文件各元素的含义和使用。
在Struts 2中,如果客户端请求中包含有中文数据,那就很容易出现中文乱码问题。当然我们会有很多方法来解决这个问题,在struts.xml中可以通过
/index.jsp
/fail.jsp
在struts.xml中,
在Java项目中,类通常都需要“包(package)”来进行组织和管理。同样的道理,struts.xml中众多的Action也需要进行统一的组织和管理,所以Struts 2的配置文件也引入了“包(package)”,并通过“包(package)”来管理Action和拦截器。
在struts.xml文件中,package元素用来配置包,配置时通常需要指定以下三个属性:
name:必需属性,指定包的名字,不允许重复。
extends:可选属性,指定要继承的包,可以继承其它包中定义的action、拦截器等。
namespace:可选属性,定义该包的命名空间(命名空间将在下一节讲述)。
/index.jsp
/fail.jsp
在上述代码中,我们使用package元素配置了一个包,名字是“admin”,并继承了“struts-default”包。
通过使用extends,你可以指定本package继承另外一个package的所有的配置。当某个package继承了另外一个package的所有配置,那么你就无需对父package中已经声明过的配置定义做再次的定义。
同时,如果重复定义父package中已声明过的配置定义,那么这些重复定义声明将覆盖父package中的相关定义。
上例中,我们让admin包继承了struts-default包,那么这个包又在哪里呢?struts-default是Struts 2默认的包,该包定义在struts-defalut.xml文件中。该文件是Struts 2框架自带的配置文件,为框架提供诸多默认配置并在运行时自动被加载。
现在我们打开struts2-core-2.1.8.1.jar,在jar包中可以找到struts-default.xml文件。打开struts-default.xml后,内容如下:
dojo\..*
input,back,cancel,browse
input,back,cancel,browse
在上面的内容中,我们看到定义了一个包 名字是struts-default,其中引用了一系列的系统拦截器,并定义了一些拦截器栈和默认拦截器,admin包就是继承了这个struts-default包,也就继承到了里面的默认拦截器的配置,通过第一章的学习,我们知道action运行前后会有一系列的拦截器自动运行,也就是因为继承了struts-default包的缘故!
因此,正常情况下,自定义的action在配置的时候要确保直接或者间接的继承到struts-default,否则action可能无法正常工作。因为struts2很多核心功能都是拦截来实现的,如,从请求中把请求参数封闭到action,文件上传和数据验证等都是通过拦截器实现的,struts-default定义了这些拦截器和Result类型,可以这么说,当包继承了struts-default才能使用struts2提供的核心功能,
4.1.3 命名空间配置
在实际应用中,同一个struts.xml文件中可能会出现同名的Action,为了便于管理,Struts 2通过命名空间来区分同名Action,即Struts 2通过Action的逻辑名和其所在的命名空间来标识一个Action。一个命名空间中不能存在同名的Action,不同的命名空间可以存在同名的Action。在struts.xml文件中,通过给包(package)指定namespace属性来为Action设置命名空间。
/list.jsp
在上述代码中,我们配置了一个名字是“admin”的包,并指定其命名空间为“/admin”,这时在访问包中名字是“query”的action时,应该这样写:
http://localhost:8080/web应用名/admin/query.action
在URL中必须指明命名空间的名字,如果一个包没有配置命名空间,那默认为“”。此时就应该这样写:
http://localhost:8080/web应用名/query.action
在实际应用中,Struts 2配置文件可能会变的十分庞大,这不利于管理和维护。这时我们可以把一个Struts 2配置文件拆分成若干个配置文件,这样也有利用团队协作开发。
/index.jsp
/fail.jsp
/list.jsp
query.action
/error.jsp
上述代码配置了三个Action,下面我们把这个配置文件拆分成两个,分别是struts1.xml和struts2.xml,代码如下所示。
struts1.xml
/index.jsp
/fail.jsp
struts2.xml
/list.jsp
query.action
/error.jsp
拆分之后,在struts.xml文件中通过include元素来包含struts1.xml和struts2.xml,代码如下所示。
include元素的file属性用来设置配置文件的路径。
Struts 2框架有两个配置文件,一个是我们前面常用的struts.xml,另一个是struts.properties。这个文件是struts2框架的全局属性配置文件。
struts.properties是一个标准的Properties文件,该文件是由一系列的key-value(键值对)组成,每个key就是一个Struts 2的属性,该key对应的value就是一个Struts 2的属性值。该文件通常放在Web应用的WEB-INF/classes目录下,Struts 2框架会自动加载这个文件。
下面将该文件常用的配置属性详细地列举出来。
struts.i18n.encoding
设置Web应用的默认编码集。此属性对于处理中文请求参数很有用,通常设置为GB2312或者utf-8。
struts.multipart.parser
该属性设置处理multipart/form-data的MIME类型请求的框架,该属性支持cos、pell和jakarta等属性值,即分别对应使用cos的文件上传框架、pell上传及common-fileupload文件上传框架,该属性的默认值为jakarta。文件上传会在后面的章节中介绍。
struts.multipart.saveDir
该属性设置上传文件的临时保存路径,该属性的默认值是javax.servlet.context.tempdir。
struts.multipart.maxSize
该属性设置Struts 2文件上传中整个请求内容允许的最大字节数。
struts.action.extension
该属性设置需要Struts 2处理的请求后缀,该属性的默认值是action,即所有匹配*.action的请求都由Struts 2处理。如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开。
struts.configuration.xml.reload
该属性设置当struts.xml文件改变后,系统是否自动重新加载该文件。该属性的默认值是false。
struts.custom.i18n.resources
该属性指定Struts 2应用所需要的国际化资源文件,如果有多个国际化资源文件,则多个资源文件的文件名以英文逗号(,)隔开。
struts.configuration.files
该属性指定Struts 2框架默认加载的配置文件,如果需要指定默认加载多个配置文件,则多个配置文件的文件名之间以英文逗号(,)隔开。该属性的默认值为struts-default.xml,struts-plugin.xml,struts.xml,看到该属性值后大家应该明白为什么Struts 2框架会默认加载struts.xml文件了。
struts.properties文件的内容均可在struts.xml中以
sturt2中搜索加载常量的顺序是:
struts-default.xml
struts-plugin.xml
struts.xml
sturts.propreties
web.xml
如果在struts.xml中做了主题的配置:
同时在struts.properties中做了主题的配置:
struts.ui.theme=simple
并使用struts2标签开发页面如下:
运行程序效果如图4.1.1所示:
图4.1.1 主题配置
显然 Struts.properties中的主题配置效力高于struts.xml。
在使用Struts 2框架进行开发时,开发者需要编写大量的Action,这是应用的核心。Action的主要作用有三个:
Ø 封装客户端请求数据
Ø 处理业务
Ø 返回结果
在上一章我们说过Struts 2的Action可以不用继承任何类和实现任何接口,它可以作为一个普通的JavaBean来使用。但是在实际应用中,我们仍需要一些接口和类来简化Action。
1. Action接口
为了使开发者编写的Action更加规范,Struts 2提供了Action接口,下面是该接口的源代码。
package com.opensymphony.xwork2;
public interface Action {
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
public String execute() throws Exception;
}
由上述代码可知,Action接口中定义了五个字符串常量:SUCCESS、NONE、ERROR、INPUT、LOGIN。我们在execute方法中返回的字符串就来自这里。自定义的Action可以不实现该接口,开发者可以在execute方法中自定义返回字符串。
2. ActionSupport类
我们自定义的Action通常继承com.opensymphony.xwork2.ActionSupport类,此类实现了一些有用的接口(包括上面的Action接口),提供了国际化、数据验证等很多实用功能。该类的内容会在后续章节中学习。
在上一章的任务实训部分,我们使用Struts 2实现了对管理员的增、删、改、查等操作。在实现过程中,每个请求都对应一个Action,例如:添加管理员对应的是AddAction,查询管理员对应的是QueryAction,删除管理员对应的是DelAction。在实际应用中,类似的情况经常出现,即:随着项目规模的扩大,我们不得不管理大量的Action,Struts 2框架提供了动态方法调用来解决这个问题,也就是说我们可以在一个Action中自定义不同的方法来处理多个请求。
DMI(Dynamic Method Invocation)即动态方法调用,这时不能只请求某个Action,还要使用感叹号(!)来标识出要调用的方面名,语法如下所示:
语法
Action的逻辑名!Action中的方法名 .action
示例4.1
下面我们把上一章的实训任务通过动态方法调用进行优化,这时只需要一个Action,代码如下所示:
public class AdminAction {
private int id;
private String logName;
private String logPwd1;
private AdminDao ad=new AdminDao();
public String addAdmin() { //添加管理员
if(ad.addAdmin(logName, logPwd1))
return "success";
else
return "fail";
}
public String queryAdmin() { //查询管理员
List adminList=ad.getAllAdmin();
Map req=(Map)ActionContext.getContext().get("request");
req.put("adminList", adminList);
return "list";
}
public String delAdmin(){ //删除管理员
if(ad.delAdmin(id))
return "success";
else
return "fail";
}
//省略getter和setter方法
}
在上述代码中,AdminAction没有继承任何类和实现任何接口,我们在该类中自定义了三个方法分别实现对管理员的增加、删除和查询,返回字符串也是自定义的。
提示
Action中的自定义方法必须是无参的,返回类型必须是String
下面是struts.xml的代码,这时的配置文件就精简了许多。
admin!queryAdmin.action
/error.jsp
/list.jsp
addAdmin.html视图页面的表单修改如下,其他代码不变。
list.jsp视图页面的【删除】超链接修改如下,其他代码不变。
通过动态方法调用优化后的项目更加精炼,方便以后的维护。
DMI在实际应用中可能会出现安全隐患,因为一旦某人知道了Action的名字和其中的方法名,他就可以通过URL进行任意的调用。Struts 2框架提供了一种更加安全的方式来实现DMI,在配置Action时可以通过method属性来指定需要调用的方法。
示例4.2
下面我们通过method属性来修改示例4.1,struts.xml代码如下所示:
/error.jsp
/list.jsp
query.action
/error.jsp
query.action
/error.jsp
在上述代码中,我们配置了三个
在struts.xml文件中可以通过通配符简化
示例4.3
下面我们把示例4.2通过通配符进行优化,代码如下所示。
queryAdmin.action
/error.jsp
/list.jsp
name属性的值是“*”,表示允许这个Action可以匹配任何以“.action”结束的URL。method属性的值是“{1}”,表示该属性的值是name属性值中的第一个“*”。例如:
http://localhost:8080/Struts6/queryAdmin.action
当使用上面的URL请求时,“*”和“{1}”就变成了“queryAdmin”,正好是Action中实现添加管理员的方法名。
http://localhost:8080/Struts6/delAdmin.action?id=40
当使用上面的URL请求时,“*”和“{1}”就变成了“delAdmin”,正好是Action中实现删除管理员的方法名。
通过通配符实现动态方法调用不仅避免了安全隐患,还简化了struts.xml的配置,建议在实际应用中使用这种方法进行开发。
如果请求的Action不存在,那么页面上可能会呈现HTTP 404错误,为了避免这种情况的发生,Struts 2框架可以指定一个默认的Action,如果没有一个Action匹配客户端请求,那么这个默认的Action就会被执行。
在struts.xml中,通过
/error.jsp
... ...
Struts 2的Action处理完用户请求后会返回一个字符串,该字符串表示逻辑视图名。struts.xml文件通过
常用的结果类型有dispatcher类型和redirect类型。
Ø dispatcher类型相当于“转发”,request、session等对象都会被转发到视图页面。
Ø redirect类型相当于“重定向”,将会丢失request、session等对象。
如果不设置type属性,默认的类型是dispatcher。
在某些情况下,事先并不能确定使用哪个结果视图,必须在程序运行期间才能确定,这时如何在struts.xml中进行配置呢?我们可以在配置时使用表达式,在程序运行时,由框架根据表达式的值来确定要使用哪个结果视图,这就是动态结果。
下面我们通过一个案例来演示动态结果的用法,完善登录案例,如果是普通用户就跳转到user.jsp,如果是管理员就跳转到admin.jsp。用户的身份必须在程序运行过程中才能确定。
示例4.4
需要在原来的Action中增加一个属性用来标识用户的身份,并提供getter/setter方法,代码如下所示。
public class AdminAction {
//省略其他属性
private String flag;
public String login()
{
if(user.isAdmin())
flag="admin";
else
flag="user";
return "success";
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
//省略其他业务方法
//省略其他getter和setter方法
}
如果是普通用户,那么flag属性的值被设置为“user”,如果是管理员,那么flag属性的值被设置为“admin”。
下面我们看一下在struts.xml文件中如何配置,代码如下所示。
/${flag}.jsp
/fail.jsp
注意上述代码中的加粗部分,我们把视图页面的名字用“${ flag }”代替,“${ flag }”表达式可以获得Action中flag属性的值。在程序运行时,如果是管理员登录,该表达式的值为admin,即跳转到admin.jsp;如图是普通用户登录,该表达式的值为user,即跳转到user.jsp。
在之前的项目中,我们都是把
全局结果定义在包(package)中,而不是某个
/error.jsp
/index.jsp
在struts.xml文件中使用
在Struts 1中,ActionForm用来封装客户端请求数据。在Struts 2中,没有了ActionForm,使用Action就可以封装客户端请求数据,例如登录案例中的Action,代码如下所示。
public class AdminAction extends ActionSupport {
private int id;
private String name;
private String pwd;
public String execute()
{
}
//省略getter和setter方法
}
与之对应的表单如下所示。
注意上述代码中的加粗部分,表单中每一个元素的name都对应Action中的一个属性,程序在运行时,Struts 2通过Action的属性来封装客户端请求数据,这种方式称之为属性驱动。我们前面所编写的案例采用的都是这种方式。
属性驱动使得Action既要封装客户端请求数据,还要处理业务,这就造成Action承担了过多的职责,分工也不够清晰。更好的解决办法就是采用单独的Model(模型)来封装请求数据,这就是模型驱动。模型驱动中的Model(模型)其实就是一个实体类或POJO,专门用来封装客户端请求数据。
示例4.5
下面我们把前面的登录案例改成模型驱动。首先需要一个封装登录表单的实体类Admin作为模型,代码如下所示。
public class Admin {
private String name; //登录名称
private String pwd; //登录密码
//省略getter和setter方法
}
Action就不需要分散的属性来封装表单数据了,只需要一个Admin类型的属性即可,代码如下所示。
public class LoginAction extends ActionSupport {
private Admin admin;
public String execute() {
AdminDao ad=new AdminDao();
if(ad.checkLogin(admin.getName(), admin.getPwd()))
return SUCCESS;
else
return ERROR;
}
public Admin getAdmin() {
return admin;
}
public void setAdmin(Admin admin) {
this.admin = admin;
}
}
与之对应的表单需要进行如下修改。
注意上述代码中的加粗部分,在使用模型驱动时,表单元素name属性的值由两部分组成,第一部分是Action中模型对象的名字,第二部分是模型对象的属性名。如果在实际应用中需要在页面上输出模型对象中属性的值,则可以使用“${ admin.name }”进行输出。
本章总结
Struts 2的配置文件
(1)中文乱码处理。可以通过名为struts.i18n.encoding的常量配置字符集。
(2)包配置。包的常用属性有name, extends和namaspaces。
(3)包含配置。
Struts 2的Action
(1)动态方法调用。
(2)Method参数和通配符的使用。
Result配置
属性驱动和模型驱动
任务实训部分
训练技能点
Ø method属性
Ø 通配符
需求说明
在第二章的核心任务部分我们讲解了一个查询图书的案例,现在要求使用Struts 2进行
重构,并应用method属性和通配符进行优化处理
实现步骤
(1) 创建对应图书表的实体类Book.java
(2) 创建实现数据库连接和关闭的工厂类DaoFactory.java
(3) 创建实现图书查询功能的Dao类BookDao.java
(4) 创建实现图书查询业务的Action类QueryAction.java,参考代码如下所示。
public class QueryAction{
private String keywords;
private BookDao bd=new BookDao();
private List bookList=new ArrayList();
private ActionContext ac=ActionContext.getContext();
private Map req=(Map)ac.get("request");
public String queryByName() //按书名查询
{
bookList=bd.getBooks("name", keywords);
req.put("bookList", bookList);
return "success";
}
public String queryByAuthor() //按作者查询
{
bookList=bd.getBooks("author", keywords);
req.put("bookList", bookList);
return "success";
}
public String queryByPublisher() //按出版社查询
{
bookList=bd.getBooks("publish", keywords);
req.put("bookList", bookList);
return "success";
}
//省略getter和setter方法
}
(5) 创建实现查询界面的视图query.html,参考代码如下所示。
由于本案例要使用动态方法调用,所以在该页面我们需要使用JS动态合成请求URL,运行效果如图4.2.1所示。
图4.2.1 查询界面
(6) 编辑struts.xml,参考代码如下所示。
/list.jsp
(7) 显示查询结果的视图页面list.jsp这里不再多述,运行效果如图4.2.2所示。
图4.2.2 查询结果
训练技能点
动态方法调用
模型驱动
需求说明
使用动态方法调用和模型驱动优化上一章的实训任务4
实现步骤
(1) 创建实体类Calculator.java,参考代码如下所示。
public class Calculator {
private double num1;
private double num2;
private double result;
//省略getter和setter方法
}
(2) 创建Action类CalculatorAction.java,参考代码如下所示。
public class CalculatorAction {
private Calculator cal;
public String jia() //加法运算
{
cal.setResult(cal.getNum1()+cal.getNum2());
return "result";
}
public String jian() //减法运算
{
cal.setResult(cal.getNum1()-cal.getNum2());
return "result";
}
public String cheng() //乘法运算
{
cal.setResult(cal.getNum1()*cal.getNum2());
return "result";
}
public String chu() //除法运算
{
cal.setResult(cal.getNum1()/cal.getNum2());
return "result";
}
//省略getter和sertter方法
}
(3) 创建计算器的视图页面,参考代码如下所示。
由于要使用动态方法调用和通配符,所以在页面中我们使用JS合成请求URL。同时我们使用EL表达式输出了Action中属性的值。
(4) 配置struts.xml,代码如下所示。
/calculator.jsp
图4.2.3 计算器
训练技能点
模型驱动
需求说明
把上一章的前三个实训任务使用模型驱动进行优化
巩固练习
一、选择题
1. 以下关于
A. name属性是必须的
B. name属性不能唯一确定一个Action
C. method属性表示调用Action中的哪个方法
D. 通配符除了星号(*)还有下划线(_)
2. 以下关于
A. type属性指定结果类型
B. name属性指定结果的逻辑名
C. name属性的值必须是Action接口中的某个常量
D. 默认的类型是dispatcher
3. Struts 2的结果类型有()。
A. dispatcher
B. redirect
C. chain
D. success
4. DMI的正确写法是()。
A. Action的逻辑名!Action中的方法名 .action
B. Action的逻辑名_Action中的方法名 .action
C. Action的逻辑名!Action中的方法名 .do
D. Action的逻辑名_Action中的方法名 .do
5. 以下关于Struts 2配置文件说法正确的是()。
A. struts.properties是必须的配置文件
E. struts.properties通过标签元素进行配置
F. 只能同时使用struts.properties与struts.xml中的一个
G. struts.properties中的配置通常都可以在struts.xml中进行配置
二、上机练习
把上一章的课后上机练习使用模型驱动和动态方法调用进行重构。运行效果图如下。
图4.3.1 增加【修改】超链接
图4.3.2 密码修改页面