1). 搭建 Struts2 的開發環境
2). 不需要顯式的定義 Filter, 而使用的是 struts2 的配置文件.
3). details.jsp 比先前變得簡單了.
${requestScope.product.productName} -> ${productName}
4). 步驟:
I. 由 product-input.action 轉到 /WEB-INF/pages/input.jsp
在 struts2 中配置一個 action
<action name="product-input">
<result>/WEB-INF/pages/input.jsp</result>
</action>
II. 由 input.jsp 頁面的 action: product-save.action 到 Product's save, 再到 /WEB-INF/pages/details.jsp
<action name="product-save" class="com.atguigu.struts2.helloworld.Product"
method="save">
<result name="details">/WEB-INF/pages/details.jsp</result>
</action>
在 Prodcut 中定義一個 save 方法, 且返回值為 details
5. result:
1). result 是 action 節點的子節點
2). result 代表 action 方法執行后, 可能去的一個目的地
3). 一個 action 節點可以配置多個 result 子節點.
4). result 的 name 屬性值對應著 action 方法可能有的一個返回值.
<result name="index">/index.jsp</result>
5). result 一共有 2 個屬性, 還有一個是 type: 表示結果的響應類型
6). result 的 type 屬性值在 struts-default 包的 result-types 節點的 name 屬性中定義.
常用的有
dispatcher(默認的): 轉發. 同 Servlet 中的轉發.
redirect: 重定向
redirectAction: 重定向到一個 Action
注意: 通過 redirect 的響應類型也可以便捷的實現 redirectAction 的功能!
<result name="index" type="redirectAction">
<param name="actionName">testAction</param>
<param name="namespace">/atguigu</param>
</result>
OR
<result name="index" type="redirect">/atguigu/testAction.do</result>
> chain: 轉發到一個 Action
注意: 不能通過 type=dispatcher 的方式轉發到一個 Action
只能是:
<result name="test" type="chain">
<param name="actionName">testAction</param>
<param name="namespace">/atguigu</param>
</result>
不能是:
<result name="test">/atguigu/testAction.do</result>
1). ActionSupport 是默認的 Action 類: 若某個 action 節點沒有配置 class 屬性, 則 ActionSupport 即為
待執行的 Action 類. 而 execute 方法即為要默認執行的 action 方法
<action name="testActionSupport">
<result>/testActionSupport.jsp</result>
</action>
等同于
<action name="testActionSupport"
class="com.opensymphony.xwork2.ActionSupport"
method="execute">
<result>/testActionSupport.jsp</result>
</action>
2). 在手工完成字段驗證, 顯示錯誤消息, 國際化等情況下, 推薦繼承 ActionSupport.
1). org.apache.struts2 包下的 default.properties 中配置了 Struts2 應用個的一些常量
2). struts.action.extension 定義了當前 Struts2 應用可以接受的請求的擴展名.
3). 可以在 struts.xml 文件中以常量配置的方式修改 default.properties 所配置的常量.
<constant name="struts.action.extension" value="action,do,"></constant>
1). 什么是 WEB 資源 ?
HttpServletRequest, HttpSession, ServletContext 等原生的 Servlet API。
2). 為什么訪問 WEB 資源?
B\S 的應用的 Controller 中必然需要訪問 WEB 資源: 向域對象中讀寫屬性, 讀寫 Cookie, 獲取 realPath ....
3). 如何訪問 ?
I. 和 Servlet API 解耦的方式: 只能訪問有限的 Servlet API 對象, 且只能訪問其有限的方法(讀取請求參數, 讀寫域對象的屬性, 使 session 失效...).
> 使用 ActionContext
> 實現 XxxAware 接口
> 選用的建議: 若一個 Action 類中有多個 action 方法, 且多個方法都需要使用域對象的 Map 或 parameters, 則建議使用
Aware 接口的方式
> session 對應的 Map 實際上是 SessionMap 類型的! 強轉后若調用其 invalidate() 方法, 可以使其 session 失效!
II. 和 Servlet API 耦合的方式: 可以訪問更多的 Servlet API 對象, 且可以調用其原生的方法.
> 使用 ServletActionContext
> 實現 ServletXxxAware 接口.
復習搭建 Struts2 的開發環境: 3 個步驟
1). action: 代表一個 Struts2 的請求.
2). Action 類: 能夠處理 Struts2 請求的類.
> 屬性的名字必須遵守與 JavaBeans 屬性名相同的命名規則.
屬性的類型可以是任意類型. 從字符串到非字符串(基本數據庫類型)之間的數據轉換可以自動發生
> 必須有一個不帶參的構造器: 通過反射創建實例
> 至少有一個供 struts 在執行這個 action 時調用的方法
> 同一個 Action 類可以包含多個 action 方法.
> Struts2 會為每一個 HTTP 請求創建一個新的 Action 實例, 即 Action 不是單例的, 是線程安全的.
1). helloWorld 時, ${productName} 讀取 productName 值, 實際上該屬性并不在 request 等域對象中, 而是從值棧中獲取的.
2). ValueStack:
I. 可以從 ActionContext 中獲取值棧對象
II. 值棧分為兩個邏輯部分
> Map 棧: 實際上是 OgnlContext 類型, 是個 Map, 也是對 ActionContext 的一個引用. 里邊保存著各種 Map:
requestMap, sessionMap, applicationMap, parametersMap, attr
> 對象棧: 實際上是 CompoundRoot 類型, 是一個使用 ArrayList 定義的棧. 里邊保存各種和當前 Action 實例相關的對象.
是一個數據結構意義的棧.
Struts2 利用 s:property 標簽和 OGNL 表達式來讀取值棧中的屬性值
1). 值棧中的屬性值:
對于對象棧: 對象棧中某一個對象的屬性值
Map 棧: request, session, application 的一個屬性值 或 一個請求參數的值.
2). 讀取對象棧中對象的屬性:
若想訪問 Object Stack 里的某個對象的屬性. 可以使用以下幾種形式之一:
object.propertyName ; object['propertyName'] ; object["propertyName"]
ObjectStack 里的對象可以通過一個從零開始的下標來引用. ObjectStack 里的棧頂對象可以用 [0] 來引用,
它下面的那個對象可以用 [1] 引用.
[0].message
[n] 的含義是從第 n 個開始搜索, 而不是只搜索第 n 個對象
若從棧頂對象開始搜索, 則可以省略下標部分: message
結合 s:property 標簽: <s:property value="[0].message" /> <s:property value="message" />
3). 默認情況下, Action 對象會被 Struts2 自動的放到值棧的棧頂.
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. 把棧頂對象的屬性回顯在表單中.
4). 關于回顯: Struts2 表單標簽會從值棧中獲取對應的屬性值進行回顯.
5). 存在的問題:
getModel 方法
public Employee getModel() {
if(employeeId == null)
employee = new Employee();
else
employee = dao.get(employeeId);
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() 方法
[能解決 5) 的問題的方案]
可以為每一個 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;
}
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();
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。