1). 先会执行 ModelDrivenInterceptor 的 intercept 方法.
public String intercept(ActionInvocation invocation) throws Exception {
//获取 Action 对象: EmployeeAction 对象, 此时该 Action 已经实现了 ModelDriven 接口
//public class EmployeeAction implements RequestAware, ModelDriven<Employee>
Object action = invocation.getAction();
//判断 action 是否是 ModelDriven 的实例
if (action instanceof ModelDriven) {
//强制转换为 ModelDriven 类型
ModelDriven modelDriven = (ModelDriven) action;
//获取值栈
ValueStack stack = invocation.getStack();
//调用 ModelDriven 接口的 getModel() 方法
//即调用 EmployeeAction 的 getModel() 方法
/*
public Employee getModel() {
employee = new Employee();
return employee;
}
*/
Object model = modelDriven.getModel();
if (model != null) {
//把 getModel() 方法的返回值压入到值栈的栈顶. 实际压入的是 EmployeeAction 的 employee 成员变量
stack.push(model);
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
}
2). 执行 ParametersInterceptor 的 intercept 方法: 把请求参数的值赋给栈顶对象对应的属性. 若栈顶对象没有对应的属性, 则查询值栈中下一个对象对应的属性...
3). 注意: getModel 方法不能提供以下实现. 的确会返回一个 Employee 对象到值栈的栈顶. 但当前 Action 的 employee 成员变量却是 null.
public Employee getModel() {
return new Employee();
}
需要写成
public Employee getModel() {
employee = new Employee();
return new Employee();}
2. 使用 paramsPrepareParamsStack 拦截器栈后的运行流程
1). paramsPrepareParamsStack 和 defaultStack 一样都是拦截器栈. 而 struts-default 包默认使用的是defaultStack
2). 可以在 Struts 配置文件中通过以下方式修改使用的默认的拦截器栈
<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>
3). paramsPrepareParamsStack 拦截器在于 params -> modelDriven -> params
所以可以先把请求参数赋给 Action 对应的属性, 再根据赋给 Action 的那个属性值决定压到值栈栈顶的对象, 最后再为栈顶对象的属性赋值.
对于 edit 操作而言:
I. 先为 EmployeeAction 的 employeeId 赋值
II. 根据 employeeId 从数据库中加载对应的对象, 并放入到值栈的栈顶
III. 再为栈顶对象的 employeeId 赋值(实际上此时 employeeId 属性值已经存在)
IV. 把栈顶对象的属性回显在表单中.
struts.xml
<!-- 配置使用paramPrepareParamStack -->
<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>
EmployeeAction.java
public class EmployeeAction implements RequestAware, ModelDriven<Employee> {
private Dao dao = new Dao();
public String list(){
request.put("list", dao.getEmployeeList());
return "list";
}
public String edit(){
return "edit";
}
public String update(){
dao.updateEmployee(employee);
return "success";
}
public String delete(){
dao.deleteEmployee(employeeId);
return "success";
}
private Integer employeeId;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public Dao getDao() {
return dao;
}
public void setDao(Dao dao) {
this.dao = dao;
}
private Map<String, Object> request;
@Override
public void setRequest(Map<String, Object> map) {
request = map;
}
private Employee employee;
@Override
public Employee getModel() {
if(employeeId == null){
employee = new Employee();
}else{
employee = dao.getEmployee(employeeId);
}
return employee;
}
}
5). 存在的问题:
getModel 方法
public Employee getModel() { if(employeeId == null) <span style="white-space:pre"> </span>employee = new Employee(); else <span style="white-space:pre"> </span>employee = dao.get(employeeId); <span style="white-space:pre"> </span>return employee; }
I. 在执行删除的时候, employeeId 不为 null, 但 getModel 方法却从数据库加载了一个对象. 不需加载!
II. 指向查询全部信息时, 也 new Employee() 对象. 浪费!
6). 解决方案: 使用 PrepareInterceptor 和 Preparable 接口.
7). 关于 PrepareInterceptor
若 Action 实现了 Preparable 接口, 则 Struts 将尝试执行 prepare[ActionMethodName] 方法,
若 prepare[ActionMethodName] 不存在, 则将尝试执行 prepareDo[ActionMethodName] 方法.
若都不存在, 就都不执行.
若 PrepareInterceptor 的 alwaysInvokePrepare 属性为 false,
则 Struts2 将不会调用实现了 Preparable 接口的 Action 的 prepare() 方法
可以为每一个 ActionMethod 准备 prepare[ActionMethdName] 方法, 而抛弃掉原来的 prepare() 方法
将 PrepareInterceptor 的 alwaysInvokePrepare 属性置为 false, 以避免 Struts2 框架再调用 prepare() 方法.
如何在配置文件中为拦截器栈的属性赋值: 参看 /struts-2.3.15.3/docs/WW/docs/interceptors.html
<interceptors>
<interceptor-stack name="parentStack">
<interceptor-ref name="defaultStack">
<param name="params.excludeParams">token</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="parentStack"/>
----------------------------------源代码解析---------------------------------
public String doIntercept(ActionInvocation invocation) throws Exception {
//获取 Action 实例
Object action = invocation.getAction();
//判断 Action 是否实现了 Preparable 接口
if (action instanceof Preparable) {
try {
String[] prefixes;
//根据当前拦截器的 firstCallPrepareDo(默认为 false) 属性确定 prefixes
if (firstCallPrepareDo) {
prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
} else {
prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
}
//若为 false, 则 prefixes: prepare, prepareDo
//调用前缀方法.
PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
} else if(cause instanceof Error) {
throw (Error) cause;
} else {
throw e;
}
}
//根据当前拦截器的 alwaysInvokePrepare(默认是 true) 决定是否调用 Action 的 prepare 方法
if (alwaysInvokePrepare) {
((Preparable) action).prepare();
}
}
return invocation.invoke();
}
PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes) 方法:
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
//获取 Action 实例
Object action = actionInvocation.getAction();
//获取要调用的 Action 方法的名字(update)
String methodName = actionInvocation.getProxy().getMethod();
if (methodName == null) {
// if null returns (possible according to the docs), use the default execute
methodName = DEFAULT_INVOCATION_METHODNAME;
}
//获取前缀方法
Method method = getPrefixedMethod(prefixes, methodName, action);
//若方法不为 null, 则通过反射调用前缀方法
if (method != null) {
method.invoke(action, new Object[0]);
}
}
PrefixMethodInvocationUtil.getPrefixedMethod 方法:
public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
assert(prefixes != null);
//把方法的首字母变为大写
String capitalizedMethodName = capitalizeMethodName(methodName);
//遍历前缀数组
for (String prefixe : prefixes) {
//通过拼接的方式, 得到前缀方法名: 第一次 prepareUpdate, 第二次 prepareDoUpdate
String prefixedMethodName = prefixe + capitalizedMethodName;
try {
//利用反射获从 action 中获取对应的方法, 若有直接返回. 并结束循环.
return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
}
catch (NoSuchMethodException e) {
// hmm -- OK, try next prefix
if (LOG.isDebugEnabled()) {
LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString());
}
}
}
return null;
}
struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!-- 配置 Struts 可以受理的请求的扩展名 -->
<constant name="struts.action.extension" value="action,do,"></constant>
<package name="app" namespace="/" extends="struts-default">
<!-- 配置使用 paramsPrepareParamsStack 作为默认的拦截器栈 -->
<!-- 修改 PrepareInterceptor 拦截器的 alwaysInvokePrepare 属性值为 false -->
<interceptors>
<interceptor-stack name="atguigustack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="atguigustack"/>
<action name="emp-*" class="com.iThings.app.EmployeeAction"
method="{1}">
<result name="{1}">/emp-{1}.jsp</result>
<result name="success" type="redirectAction">emp-list</result>
</action>
</package>
</struts>
emp-list.jsp
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'emp-list.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<s:form action="emp-save">
<s:textfield label="FirstName" name="firstName"></s:textfield>
<s:textfield label="LastName" name="lastName"></s:textfield>
<s:textfield label="Email" name="email"></s:textfield>
<s:submit></s:submit>
</s:form>
<br>
<table cellpadding="10" cellspacing="0" border="1">
<thead>
<tr>
<td>ID</td>
<td>FirstName</td>
<td>LastName</td>
<td>Email</td>
<td>Edit</td>
<td>Delete</td>
</tr>
</thead>
<tbody>
<s:iterator value="#request.emps">
<tr>
<td>${employeeId }</td>
<td>${firstName }</td>
<td>${lastName }</td>
<td>${email }</td>
<td><a href="emp-edit.action?employeeId=${employeeId }">Edit</a></td>
<td><a href="emp-delete.action?employeeId=${employeeId }">DELETE</a></td>
</tr>
</s:iterator>
</tbody>
</table>
</body>
</html>
emp-edit.jsp
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'emp-update.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<s:form action="emp-update.action">
<s:hidden name="employeeId"></s:hidden>
<s:textfield name="firstName" label="FirstName"></s:textfield>
<s:textfield name="lastName" label="LastName"></s:textfield>
<s:textfield name="email" label="Email"></s:textfield>
<s:submit></s:submit>
</s:form>
</body>
</html>
Employee.java
package com.iThings.entity;
public class Employee {
private Integer employeeId;
private String firstName;
private String lastName;
private String email;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Employee(Integer employeeId, String firstName, String lastName,
String email) {
super();
this.employeeId = employeeId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public Employee() {
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Employee [employeeId=" + employeeId + ", firstName="
+ firstName + ", lastName=" + lastName + ", email=" + email
+ "]";
}
}
EmployeeDao.java
package com.iThings.dao;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.List;
import com.iThings.entity.Employee;
public class EmployeeDao {
private static Map<Integer,Employee> emps = new LinkedHashMap<Integer, Employee>();
static{
emps.put(1001, new Employee(1001, "AA", "aa", "aa@163.com"));
emps.put(1002, new Employee(1002, "BB", "bb", "bb@163.com"));
emps.put(1003, new Employee(1003, "CC", "cc", "cc@163.com"));
emps.put(1004, new Employee(1004, "DD", "dd", "dd@163.com"));
emps.put(1005, new Employee(1005, "EE", "ee", "ee@163.com"));
}
//查询--批量
public List<Employee> getEmployeeList(){
return new ArrayList<Employee>(emps.values());
}
//查询--单个
public Employee getEmployeeById(Integer empId){
return emps.get(empId);
}
//保存
public void saveEmployee(Employee employee){
//保存时生成ID
long empId = System.currentTimeMillis();
employee.setEmployeeId((int) empId);
emps.put(employee.getEmployeeId(), employee);
}
//删除
public void delEmployee(Integer empId){
emps.remove(empId);
}
//修改
public void updateEmployee(Employee employee){
emps.put(employee.getEmployeeId(), employee);
}
}
EmployeeAction.java
package com.iThings.app;
import java.util.List;
import java.util.Map;
import org.apache.struts2.interceptor.RequestAware;
import com.iThings.dao.EmployeeDao;
import com.iThings.entity.Employee;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
public class EmployeeAction implements RequestAware, ModelDriven<Employee>, Preparable {
private Employee employee;
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
private EmployeeDao employeeDao = new EmployeeDao();
private Map<String,Object> request;
@Override
public void setRequest(Map<String, Object> arg0) {
// TODO Auto-generated method stub
this.request = arg0;
}
public String list(){
List<Employee> employeeList = employeeDao.getEmployeeList();
request.put("emps", employeeList);
return "list";
}
public String save(){
employeeDao.saveEmployee(employee);
return "success";
}
public void prepareSave(){
employee = new Employee();
}
public String delete(){
employeeDao.delEmployee(employeeId);
return "success";
}
public String edit(){
return "edit";
}
public void prepareEdit(){
employee = employeeDao.getEmployeeById(employeeId);
}
public String update(){
employeeDao.updateEmployee(employee);
return "success";
}
public void prepareUpdate(){
employee = new Employee();
}
@Override
public Employee getModel() {
return employee;
}
@Override
public void prepare() throws Exception {
// TODO Auto-generated method stub
}
}