Struts2_CRUD操作實例
先會到StrutsPrepareAndExecuteFilter的doFilter方法,
然後創建了一個StrutsActionProxy(代理),調用了這個代理的execute方法,
StrutsActionProxy裏有一個DefaultActionInvocation的引用,調用了DefaultActionInvocation裏的invoke()方法,
接着DefaultActionInvocation去調攔截器的intercept()方法,
攔截器ExceptionMappingInterceptor接着又回調DefaultActionInvocation的invoke()方法,
DefaultActionInvocation再調下一個攔截器的intercept()方法,然後再回調,
如此往復嗎,到調完最後一個攔截器再回調後,將調用自己的invokeAction(),
最終調用Action的目標方法。
Parameters攔截器將把表單字段映射到ValueStack棧的棧頂對象的各個屬性中,如果某個字段在模型裏沒有匹配的屬性,Param攔截器將嘗試ValueStack棧中的下一個對象。
默認情況下,棧頂對象就是Action。
把Action和Model隔開
在使用Struts作爲前端的企業級應用程序時把Action和Model清晰的隔離開是有必要的,有些Action類不代表任何Model對象,他們的功能僅限於提供顯示服務。
如果Action類實現了ModelDriven接口,該攔截器將把ModelDriven接口的getModel()方法返回的對象置於棧頂
1.Action實現ModelDriven接口後的運行流程
1).先會執行ModelDrivenInterceptor的interceptor方法
public String intercept(ActionInvocation invocation) throws Exception {
//獲取Action對象:EmployeeAction對象,此時該Action已經實現了ModelDriven接 口
Object action = invocation.getAction();
//判斷action是否是ModelDriven的實例
if (action instanceof ModelDriven) {
//強制轉換爲ModelDriven類型
ModelDriven modelDriven = (ModelDriven) action;
//獲取值棧
ValueStack stack = invocation.getStack();
//調用ModelDriven接口的getModel()方法
//即調用EmployeeAction的getModel()方法
Object model = modelDriven.getModel();
if (model != null) {
//把getModel()方法的返回值壓入到值棧的棧頂,實際壓入的是
//EmployeeAction的employee成員變量
stack.push(model);
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
}
3).注意:getModel方法
public Employee getModel() {
// TODO 自動生成的方法存根
employee = new Employee();
return employee;
}
不能寫成return new Employee();,這樣與成員變量employee 就沒有了聯繫,當前Action的employee成員變量是null;如下圖:當用戶觸發add請求時,ModelDriven攔截器將調用EmployeeAction對象的getModel()方法,並把返回的模型(Employee實例)壓入到ValueStack棧。
接下來Parameters攔截器將把表單字段映射到ValueStack棧的棧頂對象的各個屬性中,因爲此時ValueStack棧的棧頂元素則是剛被壓入的模型(Employee)對象,所以該模型將被填充。如果某個字段在模型裏沒有匹配的屬性,Param攔截器將嘗試ValueStack棧中的下一個對象。
1.客戶端發送請求 employee.edit
2.getModel()把employee對象置於棧頂,
此時棧頂對象爲employee,所以把請求參數賦給employee的對應屬性
3.public String edit()方法
1. 從數據庫中獲取了employeeId對應的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.把棧頂對象的屬性回顯在表單中。
該實例中的問題:
I.在執行刪除的時候,employeeId不爲空,但getModel()方法(employee = dao.get(employeeId);)卻從數據庫中加載了一個對象,沒有必要。
II.在執行list()時,會employee = new Employee(); new了個 Employee()對象,沒有必要。
解決方案:使用ParamPrepareInterceptor 的 intercept方法
關於PrepareInterceptor,源代碼解析:
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方法:public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
//獲取Action實例
Object action = actionInvocation.getAction();
//獲取要調用的Action方法的名字(update)
String methodName = actionInvocation.getProxy().getMethod();
//如果方法是空就調用execute
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);
//若方法不爲空,則通過反射調用前綴方法
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;
}
分析後得到的結論:
若prepare[ActionMethodName]不存在,則將嘗試執行prepareDo[ActionMethodName]方法,
若都不存在,就都不執行。
若ParamPrepareInterceptor 的alwaysInvokePrepare屬性爲false,則Struts2將不會調用實現了Preparable接口的Action的prepare()方法
解決方案:
爲每一個ActionMethod準備prepare[ActionMethodName]方法,而拋棄掉原來的prepare()方法
將ParamPrepareInterceptor 的alwaysInvokePrepare屬性置爲false,以避免Struts2框架再調用prepare()方法
如何在 配置文件中把攔截器棧的屬性賦值:參看文檔
使用paramsPrepareParamsStack
paramsPrepareStack從字面上理解來說,這個stack的攔截器調用的順序爲:首先params,然後prepare,接下來modelDriven,最後params。
struts2的設計上要求modelDriven在params之前調用,而業務中prepare要負責準備model,準備model又需要參數,這就需要在prepare之前運行params攔截器設置相關參數,這個也就是創建paramsPrepareParamsStack的原因。
流程如下:
1.params攔截器首先給action中的相關參數賦值,如id
2.prepare攔截器執行prepare方法,prepare方法會根據參數,如id,去調用業務邏輯,設置model對象
3.modelDriven攔截器將model對象壓入ValueStack,這裏的model對象就是在prepare中創建的
4.params攔截器再次將參數賦值給model對象
5.action的業務邏輯執行
<?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>
<package name="wul" namespace="/" extends="struts-default">
<!--配置使用paramsPrepareParamsStack作爲默認的攔截器棧
<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>
-->
<!--修改ParamPrepareInterceptor中alwaysInvokePrepare的屬性值爲false -->
<interceptors>
<interceptor-stack name="wulstack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="wulstack"></default-interceptor-ref>
<action name="emp-*" class="com.wul.app.EmployeeAction"
method="{1}">
<result name="{1}">/emp-{1}.jsp</result>
<result name="success" type="redirectAction">emp-list</result>
</action>
</package>
</struts>
注意:<interceptors>
<interceptor-stack name="wulstack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
<a href="emp-list.action">List All Employees</a>
</body>
</html>
emp-list.jsp<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
<s:form action="emp-save">
<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>
<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?employeeId=${employeeId}">Edit</a></td>
<td><a href="emp-delete?employeeId=${employeeId}">Delete</a></td>
</tr>
</s:iterator>
</tbody>
</table>
</body>
</html>
emp-edit.jsp<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
<s:debug></s:debug>
<s:form action="emp-update">
<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.javapackage com.wul.app;
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() {
super();
}
public Employee(Integer employeeId, String firstName, String lastName,
String email) {
super();
this.employeeId = employeeId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
}
Dao.java
package com.wul.app;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Dao {
private static Map<Integer,Employee> emps = new LinkedHashMap<Integer,Employee>();
static{
emps.put(1001, new Employee(1001,"AA","aa","[email protected]"));
emps.put(1002, new Employee(1002,"BB","bb","[email protected]"));
emps.put(1003, new Employee(1003,"CC","cc","[email protected]"));
emps.put(1004, new Employee(1004,"DD","dd","[email protected]"));
emps.put(1005, new Employee(1005,"EE","ee","[email protected]"));
}
public List<Employee> getEmployee(){
return new ArrayList<>(emps.values());
}
public void delete(Integer empId){
emps.remove(empId);
}
public void save(Employee emp){
long time =System.currentTimeMillis();
emp.setEmployeeId((int)time);
emps.put(emp.getEmployeeId(), emp);
}
public Employee get(Integer empId){
return emps.get(empId);
}
public void update(Employee emp){
emps.put(emp.getEmployeeId(), emp);
}
}
EmployeeAction.java
package com.wul.app;
import java.sql.PreparedStatement;
import java.util.Map;
import org.apache.struts2.interceptor.RequestAware;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
public class EmployeeAction implements RequestAware,ModelDriven<Employee>,Preparable{
private Dao dao = new Dao();
private Employee employee;
public String save(){
dao.save(employee);
return "success";
}
public void prepareSave(){
employee = new Employee();
}
public String delete(){
dao.delete(employeeId);
return "success";
}
public void prepareEdit(){
employee = dao.get(employeeId);
}
public String edit(){
//1.獲取傳入的employeeId:employee.getEmployeeId()
//2.根據employee獲取Employee對象
// Employee emp = dao.get(employee.getEmployeeId());
//3.把棧頂對象的屬性裝配好
//目前的employee對象只有employeeId屬性,其他屬性爲null
/*
Struts2表單回顯時:從值棧棧頂開始查找匹配的屬性,若找到就添加
到value屬性中。
*/
// employee.setEmail(emp.getEmail());
// employee.setFirstName(emp.getFirstName());
// employee.setLastName(emp.getLastName());
// employee = dao.get(employee.getEmployeeId());不行,經過重寫賦值的employee對象已經不再是棧頂對象了
//手動的把從數據庫中獲取的Employee對象放到值棧的棧頂
//但此時值棧棧頂及第二個對象均爲Employee對象,有浪費
// ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));
return "edit";
}
public void prepareUpdate(){
employee = new Employee();
}
public String update(){
dao.update(employee);
return "success";
}
// //需要在當前的Employee中定義employeeId屬性。
// //以接受請求參數
// private Integer employeeId;
//
// public void setEmployeeId(Integer employeeId) {
// this.employeeId = employeeId;
// }
//
// public String delete(){
// dao.delete(employeeId);
// //返回結果的類型應爲:redirectAction
// //也可以是chain:實際上chain是沒有必要的,因爲不需要在下一個Action中
// //保留當前Action的狀態
// //還有,若使用chain,則達到目標頁面後,地址欄顯示的依然是刪除的那個連接,刷屏時會有重複提交
//
// return "success";
// }
//
public String list(){
request.put("emps", dao.getEmployee());
return "list";
}
//
// private String firstName;
// private String lastName;
// private String email;
//
// 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 String save(){
// //1.獲取請求參數:通過定義對應屬性的方式
// Employee employee = new Employee(null,firstName,lastName,email);
// //2.調用Dao的save方法
// dao.save(employee);
//
// //3.通過redirectAction的方式反應結果給emp-list
// return "success";
// }
//
private Map<String,Object> request = null;
@Override
public void setRequest(Map<String, Object> arg0) {
// TODO 自動生成的方法存根
this.request = arg0;
}
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
@Override
public Employee getModel() {
// TODO 自動生成的方法存根
//判讀是Create還是Edit。
//若爲Create,則employee = new Employee();
//若爲Edit,則employee = dao.get(employeeId);
//判定標準爲是否有employeeId這個請求參數,若有該參數,則視爲Edit,若沒有該參數,則視爲Create
//若通過employeeId來判斷,則需要在ModelDriven攔截器之前先執行一個params攔截器
//而這可以通過使用paramsPrepareParams攔截器棧來實現
//需要在struts.xml文件中配置使用paramsPrepareParams作爲默認的攔截器棧。
// if(employeeId==null)
// employee = new Employee();
// else
// employee = dao.get(employeeId);
return employee;
}
//prepare方法的主要作用:爲getModel()方法準備model的。
@Override
public void prepare() throws Exception {
// // TODO 自動生成的方法存根
// if(employeeId==null)
// employee = new Employee();
// else
// employee = dao.get(employeeId);
System.out.println("prepare...");
}
}
web.xml<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>struts2_2</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>