0x00 参考

本笔记直接参考或引自如下链接文章:

http://c.biancheng.net/struts2/

https://www.w3cschool.cn/struts_2/

0x01 Action配置详解

Action是用于处理请求操作的,它是由StrutsPrepareAndExecuteFilter分发过来的。

实现Action控制类

在 Struts2 中,一个 Action 类代表一次请求或调用,每个请求的动作都对应一个相应的 Action 类。也就是说,用户的每次请求,都会转到一个相应的 Action 类中,由这个 Action 类进行处理。简而言之,Action 就是用于处理一次用户请求的对象。

实现Action接口

当 Action 类处理用户请求成功后,有人习惯返回 index 字符串,有人习惯返回 success 字符串,这会导致在一个 Action 中可能会返回各种不同的值,十分不利于项目的统一管理。

为了让用户更规范地创建 Action 类,Struts2 提供了一个 Action 接口,该接口定义了 Action 类应该实现的规范,用户在创建 Action 时,可以实现这个接口。Action 接口中的具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
public interface Action {
//定义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";
//定义处理用户请求的execute()方法
public String execute() throws Exception;
}

从上述代码中可以看出,Action 接口位于 com.opensymphony.xwork2 包中,并且接口中只定义了五个字符串常量和一个 execute() 方法。其中,execute() 方法是 Action 类的默认请求处理方法,该方法返回一个字符串,而上面五个字符串常量的作用是统一 execute() 方法的返回值。

继承ActionSupport类

由于 Xwork 的 Action 接口十分简单,为开发者提供的帮助较小,所以在实际开发过程中,通常都是采用继承 ActionSupport 类的方式创建 Action。其示例代码如下所示:

1
2
3
4
5
6
7
public class LoginAction extends ActionSupport{
private static final long serialVersionUID = 1L;
@Override
public String execute() throws Exception{
return super.execute();
}
}

ActionSupport 是 Action 接口的默认实现类,所以继承 ActionSupport 就相当于实现了 Action 接口。除 Action 接口以外,ActionSupport 类还实现了 Validateable、ValidationAware、TextProvider、LocaleProvider 和 Serializable 等接口,这为用户提供了更多的功能。

ActionSupport 类中提供了许多默认方法,这些默认方法包括数据校验的方法、默认的处理用户请求的方法等。如果开发者的 Action 类继承 ActionSupport 类,会大大简化 Action 的开发。

需要注意的是,由于自定义的 Action 类继承了 ActionSupport 类,因此必须定义一个变量 serialVersionUID。这是因为 ActionSupport 类实现了 Serializable 接口,任何实现了 Serializable 接口的类都必须声明变量 serialVersionUID,如下所示:

1
private static final long serialVersionUID = 1L;

在学习过程中,细心的读者可能会发现,即使不加上述代码,程序也可以正常执行。但是在实际项目开发中,必须加上上述代码。

配置Action

配置 Action 主要就是配置 struts.xml 文件中 Action 的映射信息。Action 映射是指将一个请求的 URL 映射到一个 Action 类,当一个请求匹配某个 Action 名称时,Struts2 框架就使用这个 Action 确定如何处理请求。

struts.xml 文件是通过 <action> 元素对请求的 Action 和 Action 类进行配置的,其示例代码如下所示:

1
2
3
<action name="userAction" class="com.mi1k7ea.action.UserAction" method="add">
...
</action>

在上述代码中,包含了 <action> 元素的三个常用属性 name、class 和 method,这三个属性的具体说明如下表所示。

名 称 可选/必填 说 明
name 必填属性 表示 Action 的名称(该名称必须唯一),它指定了 Action 所处理请求的 URL。该属性将在其他地方被引用,如作为 JSP 页面 form 表单的 action 属性值。
class 可选属性 用于指定 Action 的实现类,如果没有指定 class 属性值,则其默认值为 com.opensymphony.xwork2.ActionSupport 类。
method 可选属性 指定请求 Action 时调用的方法。如果指定了 method 属性,则该 Action 会调用 method 属性中指定的方法,如果不指定 method 属性,则 Action 会调用 execute() 方法。

使用通配符

由于在一个 Action 类中可能有多个业务逻辑处理方法,在配置 Action 时,就需要使用多个 <action> 元素。在实现同样功能的情况下,为了减少 struts.xml 配置文件的代码量,可以借助于通配符映射信息。

下面以一段 Action 的配置代码为例,说明如何使用通配符进行配置,如下所示:

1
2
3
4
5
<package name="user" namespace="/user" extends="struts-default">
<action name="userAction_*" class="com.mi1k7ea.action.UserAction" method="{1}">
<result>/index.jsp</result>
</action>
</package>

在上述代码中,method 属性值中的数字1表示匹配第 1 个 *。当客户端发送 /user/userAction_login.action 这样的请求时,<action> 元素的 name 属性值就被设置成 userAction_login,method 属性值就被设置成 login。当客户端发送 /user/userAction_register.action 这样的请求时,<action> 元素的 name 属性值就被设置为 userAction_register,method 属性值也被设置成 register。

另外,对 <result> 元素也可以采用通配符配置,代码如下所示:

1
<result>/(1).jsp</result>

当客户端发送 userAction_login 这样的请求时,<result> 元素被设置成跳转到 login.jsp 页面。当客户端发送 userAction_register 这样的请求时,<result>元素被设置成跳转到 register.jsp 页面。

0x02 Action访问Servlet API

在 Struts2 中,虽然 Action 已经与 Servlet API 完全分离,但在实现业务逻辑时,还是经常要访问 Servlet API 中的对象。

通常开发时需要访问 Servlet API 中的 HttpServletRequest、HttpSession 和 ServletContext 三个接口,它们分别对应 JSP 内置对象 request、session 和 application。

在 Struts2 中,访问 Servlet API 通常采用两种方式,分别是通过 ActionContext 访问和通过 ServletActionContext 访问,本节将针对这两种访问方式进行讲解。

通过ActionContext访问

ActionContext 是 Action 执行的上下文对象,在 ActionContext 中保存了 Action 执行所需要的所有对象,包括 request、session 和 application 等。ActionContext 类访问 Servlet API 的几个常用方法如表所示。

方法声明 功能描述
void put(String key, Object value) 将 key-value 键值对放入 ActionContext 中,模拟 Servlet API 中的 HttpServletRequest 的 setAttribute() 方法
Object get(String key) 通过参数 key 查找当前 ActionContext 中的值
Map<String, Object> get Application() 返回一个 Application 级的 Map 对象
static ActionContext getContext() 获取当前线程的 ActionContext 对象
Map<String, Object> getParameters() 返回一个包含所有 HttpServletRequest 参数信息的 Map 对象
Map<String, Object> getSession() 返回一个 Map 类型的 HttpSession 对象

要访问 Servlet API,可以通过如下示例代码方式进行:

1
2
3
4
ActionContext context = ActionContext.getContext();
context.put("name","mi1k7ea");
context.getApplication().put("name","mi1k7ea");
context.getSession().put("name","mi1k7ea");

在上述示例代码中,通过 ActionContext 类中的方法调用,分别在 request、application 和 session 中放入了(”name”,”mi1k7ea”)键值对。通过代码可以看到,ActionContext 类可以非常简单地访问 JSP 内置对象的属性。

Demo

沿用之前的示例项目继续演示。

添加login.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>登录页面</title>
<style type="text/css">
input[type=text],input[type=password]{width:150px}
</style>
</head>
<body>
<div align="center">
<form action="login" method="post">
用户名:<input type="text" name="username"/><br/>
密&nbsp;&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"/><br/>
<input type="reset" value="重置"/>
<input type="submit" value="登录"/>
</form>
</div>
</body>
</html>

修改success.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>登录成功页面</title>
</head>
<body>
<p>${success }<br/></p>
<h2>用户登录信息</h2>
用户名:${username }<br/>
密码:${password }<br/>
</body>
</html>

添加error.jsp:

1
2
3
4
5
6
7
8
9
10
11
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>登录失败页面</title>
</head>
<body>
<p>${error }<br/></p>
</body>
</html>

编写LoginAction类,主要用于登录逻辑处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.mi1k7ea;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport {
private String username; // 用户名
private String password; // 密码
// username的getter和setter方法
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
// password的getter和setter方法
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String execute() throws Exception {
// 获取ActionContext对象
ActionContext context = ActionContext.getContext();
if ("admin".equals(username) && "123456".equals(password)) {
// 将用户名和密码信息放入context对象中
context.put("username", username);
context.put("password", password);
context.put("success", "用户登录成功!");
return SUCCESS;
} else {
// 定义登录失败的错误信息
context.put("error", "用户名或密码错误,请重新登录!");
return ERROR;
}
}
}

修改struts.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<!-- 指定 Struts2 配置文件的 DTD 信息 -->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<!-- Struts2配置文件的根元素 -->
<struts>
<!-- Struts2的Action必须放在指定的包空间下定义 -->
<package name="default" extends="struts-default">
<action name="login" class="com.mi1k7ea.LoginAction">
<result name="success">/success.jsp</result>
<result name="error">/error.jsp</result>
</action>
</package>
</struts>

效果如图:

通过ServletActionContext访问

除了通过 ActionContext 类访问 Servlet API 以外,Struts2 框架还提供了 ServletActionContext 类访问 Servlet API,该类中的方法都是静态方法,其常见方法如表所示。

方法声明 功能描述
static PageContext getPageContext() 获取 Web 应用的 PageContext 对象
static HttpServletRequest getRequest() 获取 Web 应用的 HttpServletRequest 对象
static HttpServletResponse getResponse() 获取 Web 应用的 HttpServletResponse 对象
static ServletContext getServletContext() 获取 Web 应用的 ServletContext 对象

Demo

在前面项目中的 com.mi1k7ea 包下创建 MessageAction 类,编写后的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
package com.mi1k7ea;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.ServletActionContext;

public class MessageAction extends ActionSupport {
public String execute() throws Exception{
ServletActionContext.getRequest().setAttribute("message","Access Servlet API By ServletActionContext.");
return SUCCESS;
}
}

在struts.xml的package标签内添加一个action标签:

1
2
3
<action name="message" class="com.mi1k7ea.MessageAction">
<result name="success">/message.jsp</result>
</action>

创建message.jsp:

1
<div align="center">${requestScope.message }</div>

运行效果:

0x03 Action处理请求参数

在 Struts2 框架中,页面的请求数据和 Action 有两种基本的对应方式,分别是字段驱动(FieldDriven,也称为属性驱动)方式和模型驱动(ModelDriver)方式。

属性驱动

属性驱动是指在 Action 中通过字段属性进行与页面之间的数据传递,通常使用时会包括两种情况:一种是与基本数据类型的属性对应,另一种是直接使用域对象。

基本数据类型字段驱动方式的数据传递

在 Struts2 中,可以直接在 Action 中定义各种 Java 基本数据类型的字段,使这些字段与表单数据相对应,并利用这些字段进行数据传递,如下面的代码所示:

1
2
3
4
5
6
7
8
public class UserAction extends ActionSupport {
private String username; // 用户名
private String password; // 密码
// 此处省略两个属性的getter和setter方法
private String execute() throws Exception {
return SUCCESS;
}
}

在上述 Action 类的代码中,定义了两个字符串字段 username 和 password,这两个字段分别用于对应页面上的用户名和密码这两个表单域。

直接使用域对象字段驱动方式的数据传递

在基本数据类型字段驱动方式中,如果传入的数据很多,那么 Action 的属性也会变得很多,再加上属性对应的 getter/setter 方法,势必会导致 Action 非常臃肿。

为了解决这一问题,我们可以把属性的 getter/setter 方法从 Action 中提取出来单独作为一个域对象,并在相应的 Action 中直接使用这个域对象。此种方式中的域对象一般以 JavaBean 的形式实现,JavaBean 中所封装的属性要和页面中表单的属性一一对应。此时 JavaBean 将成为数据传递的载体,并可以在其他 Action 中使用。

Demo参考:http://c.biancheng.net/view/4095.html

模型驱动

在 Struts2 中,Action 还有另外一种方式处理请求参数,称为模型驱动(ModelDriven)。

模型驱动方式要求 Action 需要通过实现 ModelDriven 接口接收请求参数,并且要重写 getModel() 方法。getModel() 方法返回的就是 Action 所使用的数据模型对象。

与属性驱动中直接使用域对象字段驱动方式的数据传递类似,模型驱动方式也是通过 JavaBean 模型进行数据传递的。只要是普通的 JavaBean,就可以充当模型部分,并且 JavaBean 中所封装的属性要与表单的属性一一对应,JavaBean 就是数据传递的载体。

使用模型驱动方式时,Action 类中通过 getModel() 方法获取模型,其示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mi1k7ea;
import com.mi1k7ea.User;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
public class LoginAction extends ActionSupport implements ModelDriven<User> {
private User user = new User();
public User getModel() {
return user;
}
public String execute() throws Exception {
return SUCCESS;
}
}

使用模型驱动时,其对应的表单页面也要做相应调整,调整后的代码片段如下所示:

1
2
3
4
5
<form action="loginAction" method="post" name="form1">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>

从上述代码中可以看出,使用 ModelDriver 的方式后,表单中的文本域名称已经不需要添加 user 前缀,页面上的 username 会自动对应到这个 Model 的 username 属性。

与属性驱动相比,模型驱动不需要在 Action 类中定义与表单元素一一对应的所有属性及其各属性的 getter 和 setter 方法,这减少了 Action 类中的代码量。在项目应用中具体使用哪种驱动方法,现给出以下几点建议。

  1. 要统一整个系统中 Action 的驱动方法,即要么都使用属性驱动,要么都使用模型驱动。
  2. 如果持久层对象与表单中的属性是一一对应的关系,那么建议使用模型驱动,因为模型驱动方法使 Action 类中的代码更加整洁。
  3. 如果持久层对象与表单中的属性不是一一对应的关系,那么建议使用属性驱动,因为不是一一对应的关系时,系统中需要提供两个 JavaBean(一个对应表单提交的数据,一个用于持久层对象)。

总之,属性驱动的方法和模型驱动的方法各有优缺点,在实际开发中,需要根据项目实际情况选择使用哪种驱动方式。