以下筆記內容來自尚硅谷_Struts2_佟剛老師的視頻教程+自己一點點整理來源免責聲明
一、
1. VS 自實現:
1). 搭建 Struts2 的開發環境
2). 不需要顯式的定義 Filter, 而使用的是 struts2 的配置文件.
3). details.jsp 比先前變得簡單了。
屬性引用:${requestScope.product.productName} -> ${productName}
4). 步驟:
I. 由 product-input.action 轉到 /WEB-INF/pages/input.jsp
在 struts2 中配置一個 action
1 <action name="product-input"> 2 <result>/WEB-INF/pages/input.jsp</result> 3 </action>
II. 由 input.jsp 頁面的 action: product-save.action 到 Product's save, 再到 /WEB-INF/pages/details.jsp
1 <action name="product-save" 2 class="com.atguigu.struts2.helloworld.Product" method="save"> 3 <result name="details">/WEB-INF/pages/details.jsp</result> 4 </action>
*在 Prodcut 中定義一個 save 方法, 且返回值為 details,與method=“save”對應
二、
1. action VS Action 類
1). action: 代表一個 Struts2 的請求.
2). Action 類: 能夠處理 Struts2 請求的類.
> 屬性的名字必須遵守與 JavaBeans 屬性名相同的命名規則. 屬性的類型可以是任意類型. 從字符串到非字符串(基本數據庫類型)之
間的數據轉換可以自動發生
> 必須有一個不帶參的構造器: 通過反射創建實例
> 至少有一個供 struts 在執行這個 action 時調用的方法
> 同一個 Action 類可以包含多個 action 方法.
> Struts2 會為每一個 HTTP 請求創建一個新的 Action 實例, 即 Action 不是單例的, 是線程安全的.
2. 在 Action 中訪問 WEB 資源:
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 接口.
3. 關於 Struts2 請求的擴展名問題
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>
4. ActionSupport
1). ActionSupport 是默認的 Action 類: 若某個 action 節點沒有配置 class 屬性, 則 ActionSupport 即為 待執行的 Action 類. 而 execute 方法即為要默認執行的 action 方法
1 <action name="testActionSupport"> 2 <result>/testActionSupport.jsp</result> 3 </action>
等同於
1 <action name="testActionSupport" 2 class="com.opensymphony.xwork2.ActionSupport" method="execute"> 3 <result>/testActionSupport.jsp</result> 4 </action>
2). 在手工完成字段驗證, 顯示錯誤消息, 國際化等情況下, 推薦繼承 ActionSupport.
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 的功能!
1 <result name="index" type="redirectAction"> 2 <param name="actionName">testAction</param> 3 <param name="namespace">/atguigu</param> 4 </result>
OR
1 <result name="index" type="redirect">/atguigu/testAction.do</result>
> chain: 轉發到一個 Action
注意: 不能通過 type=dispatcher 的方式轉發到一個 Action 只能是:
1 <result name="test" type="chain"> 2 <param name="actionName">testAction</param> 3 <param name="namespace">/atguigu</param> 4 </result>
不能是:
1 <result name="test">/atguigu/testAction.do</result>
三、
1. 關於值棧:
1). helloWorld 時, ${productName} 讀取 productName 值, 實際上該屬性並不在 request 等域對象中, 而是從值棧中獲取的.
2). ValueStack:
I. 可以從 ActionContext 中獲取值棧對象 II. 值棧分為兩個邏輯部分
> Map 棧: 實際上是 OgnlContext 類型, 是個 Map, 也是對 ActionContext 的一個引用.
裡邊保存著各種 Map: requestMap, sessionMap, applicationMap, parametersMap, attr
> 對象棧: 實際上是 CompoundRoot 類型, 是一個使用 ArrayList 定義的棧.
裡邊保存各種和當前 Action 實例相關的對象. 是一個數據結構意義的棧.
2. 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. Action 實現 ModelDriven 接口後的運行流程
1). 先會執行 ModelDrivenInterceptor 的 intercept 方法.
1 public String intercept(ActionInvocation invocation) throws Exception { 2 //獲取 Action 對象: EmployeeAction 對象, 此時該 Action 已經實現了ModelDriven 接口 3 //public class EmployeeAction implements RequestAware,ModelDriven<Employee> 4 Object action = invocation.getAction(); 5 6 //判斷 action 是否是 ModelDriven 的實例 7 if (action instanceof ModelDriven) { 8 //強制轉換為 ModelDriven 類型 9 ModelDriven modelDriven = (ModelDriven) action; 10 //獲取值棧 11 ValueStack stack = invocation.getStack(); 12 //調用 ModelDriven 接口的 getModel() 方法 13 //即調用 EmployeeAction 的 getModel() 方法 14 /* 15 public Employee getModel() { 16 employee = new Employee(); 17 return employee; 18 } 19 */ 20 Object model = modelDriven.getModel(); 21 if (model != null) { 22 //把 getModel() 方法的返回值壓入到值棧的棧頂. 實際壓入的是 EmployeeAction 的 employee 成員變量 23 stack.push(model); 24 } 25 if (refreshModelBeforeResult) { 26 invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); 27 } 28 } 29 return invocation.invoke(); 30 }
2). 執行 ParametersInterceptor 的 intercept 方法:
把請求參數的值賦給棧頂對象對應的屬性. 若棧頂對象沒有對應的屬性, 則查詢 值棧中下一個對象對應的屬性...
3). 注意: getModel 方法不能提供以下實現. 的確會返回一個 Employee 對象到值棧的棧頂. 但當前 Action 的 employee 成員變量卻是 null.
1 public Employee getModel() { 2 return new Employee(); 3 }
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 的那個屬性值決定壓到值棧棧頂的對象, 最後再為棧頂對象的屬性賦值.
對於Employee 的 edit 操作而言:
I. 先為 EmployeeAction 的 employeeId 賦值
II. 根據 employeeId 從數據庫中加載對應的對象, 並放入到值棧的棧頂
III. 再為棧頂對象的 employeeId 賦值(實際上此時 employeeId 屬性值已經存在)
IV. 把棧頂對象的屬性回顯在表單中.
4). 關於回顯: Struts2 表單標簽會從值棧中獲取對應的屬性值進行回顯.
5). 存在的問題:
getModel 方法
1 public Employee getModel() { 2 if(employeeId == null) 3 employee = new Employee(); 4 else 5 employee = dao.get(employeeId); 6 7 return employee; 8 }
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
1 <interceptors> 2 <interceptor-stack name="parentStack"> 3 <interceptor-ref name="defaultStack"> 4 <param name="params.excludeParams">token</param> 5 </interceptor-ref> 6 </interceptor-stack> 7 </interceptors> 8 9 <default-interceptor-ref name="parentStack"/>
----------------------------------源代碼解析---------------------------------
1 public String doIntercept(ActionInvocation invocation) throws Exception { 2 //獲取 Action 實例 3 Object action = invocation.getAction(); 4 5 //判斷 Action 是否實現了 Preparable 接口 6 if (action instanceof Preparable) { 7 try { 8 String[] prefixes; 9 //根據當前攔截器的 firstCallPrepareDo(默認為 false) 屬性確定 prefixes 10 if (firstCallPrepareDo) { 11 prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX}; 12 } else { 13 prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX}; 14 } 15 //若為 false, 則 prefixes: prepare, prepareDo 16 //調用前綴方法. 17 PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes); 18 } 19 catch (InvocationTargetException e) { 20 21 Throwable cause = e.getCause(); 22 if (cause instanceof Exception) { 23 throw (Exception) cause; 24 } else if(cause instanceof Error) { 25 throw (Error) cause; 26 } else { 27 throw e; 28 } 29 } 30 31 //根據當前攔截器的 alwaysInvokePrepare(默認是 true) 決定是否調用 Action 的 prepare 方法 32 if (alwaysInvokePrepare) { 33 ((Preparable) action).prepare(); 34 } 35 } 36 37 return invocation.invoke(); 38 } 39 40 PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes) 方法: 41 42 public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws 43 44 InvocationTargetException, IllegalAccessException { 45 //獲取 Action 實例 46 Object action = actionInvocation.getAction(); 47 //獲取要調用的 Action 方法的名字(update) 48 String methodName = actionInvocation.getProxy().getMethod(); 49 50 if (methodName == null) { 51 // if null returns (possible according to the docs), use the default execute 52 methodName = DEFAULT_INVOCATION_METHODNAME; 53 } 54 55 //獲取前綴方法 56 Method method = getPrefixedMethod(prefixes, methodName, action); 57 58 //若方法不為 null, 則通過反射調用前綴方法 59 if (method != null) { 60 method.invoke(action, new Object[0]); 61 } 62 } 63 64 PrefixMethodInvocationUtil.getPrefixedMethod 方法: 65 66 public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) { 67 assert(prefixes != null); 68 //把方法的首字母變為大寫 69 String capitalizedMethodName = capitalizeMethodName(methodName); 70 71 //遍歷前綴數組 72 for (String prefixe : prefixes) { 73 //通過拼接的方式, 得到前綴方法名: 第一次 prepareUpdate, 第二次 prepareDoUpdate 74 String prefixedMethodName = prefixe + capitalizedMethodName; 75 try { 76 //利用反射獲從 action 中獲取對應的方法, 若有直接返回. 並結束循環. 77 return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY); 78 } 79 catch (NoSuchMethodException e) { 80 // hmm -- OK, try next prefix 81 if (LOG.isDebugEnabled()) { 82 LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString()); 83 } 84 } 85 } 86 return null; 87 }
五、
1. 國際化的目標
1). 如何配置國際化資源文件
I. Action 范圍資源文件: 在Action類文件所在的路徑建立名為 ActionName_language_country.properties 的文件
II. 包范圍資源文件: 在包的根路徑下建立文件名為 package_language_country.properties 的屬性文件, 一旦建立,處於該包下的所有 Action 都可以訪問該資源文件。注意:包范圍資源文件的 baseName 就是package,不是Action所在的包名。
III. 全局資源文件
> 命名方式: basename_language_country.properties
> struts.xml <constant name="struts.custom.i18n.resources" value="baseName"/>
IV. 國際化資源文件加載的順序如何呢 ?
離當前 Action 較近的將被優先加載.
假設我們在某個 ChildAction 中調用了getText("username"):
(1) 加載和 ChildAction 的類文件在同一個包下的系列資源文件 ChildAction.properties
(2) 加載 ChildAction 實現的接口 IChild,且和 IChildn 在同一個包下 IChild.properties 系列資源文件。
(3) 加載 ChildAction 父類 Parent,且和 Parent 在同一個包下的 baseName 為 Parent.properties 系列資源文件。
(4) 若 ChildAction 實現 ModelDriven 接口,則對於getModel()方法返回的model 對象,重新執行第(1)步操作。
(5) 查找當前包下 package.properties 系列資源文件。
(6) 沿著當前包上溯,直到最頂層包來查找 package.properties 的系列資源文件。
(7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列資源文件。
(8) 直接輸出該key的字符串值。
2). 如何在頁面上 和 Action 類中訪問國際化資源文件的 value 值
I. 在 Action 類中. 若 Action 實現了 TextProvider 接口, 則可以調用其 getText() 方法獲取 value 值 > 通過繼承 ActionSupport 的方式。
II. 頁面上可以使用 s:text 標簽; 對於表單標簽可以使用表單標簽的 key 屬性值
> 若有占位符, 則可以使用 s:text 標簽的 s:param 子標簽來填充占位符
> 可以利用標簽和 OGNL 表達式直接訪問值棧中的屬性值(對象棧 和 Map 棧)
time=Time:{0} <s:text name="time"> <s:param value="date"></s:param> </s:text> ------------------------------------ time2=Time:${date} <s:text name="time2"></s:text>
3). 實現通過超鏈接切換語言.
I. 關鍵之處在於知道 Struts2 框架是如何確定 Local 對象的 !
II. 可以通過閱讀 I18N 攔截器知道.
III. 具體確定 Locale 對象的過程:
> Struts2 使用 i18n 攔截器 處理國際化,並且將其注冊在默認的攔截器棧中
> i18n攔截器在執行Action方法前,自動查找請求中一個名為 request_locale 的參數。 如果該參數存在,攔截器就將其作為參數,轉換成Locale對象,並將其設為用戶默認的Locale(代表國家/語言環境)。 並把其設置為 session 的 WW_TRANS_I18N_LOCALE 屬性
> 若 request 沒有名為request_locale 的參數,則 i18n 攔截器會從 Session 中獲取 WW_TRANS_I18N_LOCALE的屬性值, 若該值不為空,則將該屬性值設置為浏覽者的默認Locale > 若 session 中的 WW_TRANS_I18N_LOCALE 的屬性值為空,則從 ActionContext 中獲取 Locale 對象。
IV. 具體實現: 只需要在超連接的後面附著 request_locale 的請求參數, 值是 語言國家 代碼.
<a href="testI18n.action?request_locale=en_US">English</a> <a href="testI18n.action?request_locale=zh_CN">中文</a>
> 注意: 超鏈接必須是一個 Struts2 的請求, 即使 i18n 攔截器工作!
六、
1. Struts2 的驗證
1). 驗證分為兩種:
> 聲明式驗證*
>> 對哪個 Action 或 Model 的那個字段進行驗證 >> 使用什麼驗證規則 >> 如果驗證失敗, 轉向哪一個頁面, 顯示是什麼錯誤消息
> 編程式驗證
2). 聲明式驗證的 helloworld
I. 先明確對哪一個 Action 的哪一個字段進行驗證: age
II. 編寫配置文件:
> 把 struts-2.3.15.3\apps\struts2-blank\WEB-INF\classes\example 下的 Login-validation.xml 文件復制到 當前 Action 所在的包下.
> 把該配置文件改為: 把 Login 改為當前 Action 的名字. > 編寫驗證規則: 參見 struts-2.3.15.3/docs/WW/docs/validation.html 文檔即可.
> 在配置文件中可以定義錯誤消息:
1 <field name="age"> 2 <field-validator type="int"> 3 <param name="min">20</param> 4 <param name="max">50</param> 5 <message>Age needs to be between ${min} and ${max}</message> 6 </field-validator> 7 </field>
再在 國際化資源文件 中加入一個鍵值對: error.int=^^^Age needs to be between ${min} and ${max}
III. 若驗證失敗, 則轉向 input 的那個 result. 所以需要配置 name=input 的 result <result name="input">/validation.jsp</result>
IV. 如何顯示錯誤消息呢 ?
> 若使用的是非 simple, 則自動顯示錯誤消息.
> 若使用的是 simple 主題, 則需要 s:fielderror 標簽或直接使用 EL 表達式(使用 OGNL) ${fieldErrors.age[0]} OR <s:fielderror fieldName="age"></s:fielderror> *
3). 注意: 若一個 Action 類可以應答多個 action 請求, 多個 action 請求使用不同的驗證規則, 怎麼辦 ?
> 為每一個不同的 action 請求定義其對應的驗證文件: ActionClassName-AliasName-validation.xml
> 不帶別名的配置文件: ActionClassName-validation.xml 中的驗證規則依然會發生作用. 可以把各個 action 公有的驗證規則 配置在其中.
但需要注意的是, 只適用於某一個 action 的請求的驗證規則就不要這裡再配置了. 4). 聲明式驗證框架的原理:
> Struts2 默認的攔截器棧中提供了一個 validation 攔截器
> 每個具體的驗證規則都會對應具體的一個驗證器. 有一個配置文件把驗證規則名稱和驗證器關聯起來了. 而實際上驗證的是那個驗證器.
該文件位於 com.opensymphony.xwork2.validator.validators 下的 default.xml
<validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
5). 短路驗證: 若對一個字段使用多個驗證器, 默認情況下會執行所有的驗證. 若希望前面的驗證器驗證沒有通過, 後面的就不再驗證, 可以使用短路驗證
1 <!-- 設置短路驗證: 若當前驗證沒有通過, 則不再進行下面的驗證 --> 2 <field-validator type="conversion" short-circuit="true"> 3 <message>^Conversion Error Occurred</message> 4 </field-validator> 5 6 <field-validator type="int"> 7 <param name="min">20</param> 8 <param name="max">60</param> 9 <message key="error.int"></message> 10 </field-validator>
6). 若類型轉換失敗, 默認情況下還會執行後面的攔截器, 還會進行 驗證.
可以通過修改 ConversionErrorInterceptor 源代碼的方式使 當類型轉換失敗時, 不再執行後續的驗證攔截器, 而直接返回 input 的 result
1 Object action = invocation.getAction(); 2 if (action instanceof ValidationAware) { 3 ValidationAware va = (ValidationAware) action; 4 5 if(va.hasFieldErrors() || va.hasActionErrors()){ 6 return "input"; 7 } 8 }
7). 關於非字段驗證: 不是針對於某一個字段的驗證.
1 <validator type="expression"> 2 <param name="expression"><![CDATA[password==password2]]></param> 3 <message>Password is not equals to password2</message> 4 </validator>
8). 不同的字段使用同樣的驗證規則, 而且使用同樣的響應消息 ?
1 error.int=${getText(fieldName)} needs to be between ${min} and ${max} 2 age=\u5E74\u9F84 count=\u6570\u91CF
詳細分析參見 PPT 159.
9). 自定義驗證器:
I. 定義一個驗證器的類
> 自定義的驗證器都需要實現 Validator.
> 可以選擇繼承 ValidatorSupport 或 FieldValidatorSupport 類
> 若希望實現一個一般的驗證器, 則可以繼承 ValidatorSupport
> 若希望實現一個字段驗證器, 則可以繼承 FieldValidatorSupport
> 具體實現可以參考目前已經有的驗證器. > 若驗證程序需要接受一個輸入參數, 需要為這個參數增加一個相應的屬性
II. 在配置文件中配置驗證器
> 默認情況下下, Struts2 會在 類路徑的根目錄下加載 validators.xml 文件. 在該文件中加載驗證器.
該文件的定義方式同默認的驗證器的那個配置文件: 位於 com.opensymphony.xwork2.validator.validators 下的 default.xml
> 若類路徑下沒有指定的驗證器, 則從 com.opensymphony.xwork2.validator.validators 下的 default.xml 中的驗證器加載
III. 使用: 和目前的驗證器一樣.
IV. 示例代碼: 自定義一個 18 位身份證驗證器
七、
1. 文件的上傳:
1). 表單需要注意的 3 點
2). Struts2 的文件上傳實際上使用的是 Commons FileUpload 組件, 所以需要導入 commons-fileupload-1.3.jar commons-io-2.0.1.jar
3). Struts2 進行文件上傳需要使用 FileUpload 攔截器
4). 基本的文件的上傳: 直接在 Action 中定義如下 3 個屬性, 並提供對應的 getter 和 setter
//文件對應的 File 對象 private File [fileFieldName]; //文件類型 private String [fileFieldName]ContentType; //文件名 private String [fileFieldName]FileName;
5). 使用 IO 流進行文件的上傳即可.
6). 一次傳多個文件怎麼辦 ?
若傳遞多個文件, 則上述的 3 個屬性, 可以改為 List 類型! 多個文件域的 name 屬性值需要一致.
7). 可以對上傳的文件進行限制嗎 ? 例如擴展名, 內容類型, 上傳文件的大小 ? 若可以, 則若出錯, 顯示什麼錯誤消息呢 ? 消息可以定制嗎 ?
可以的!
可以通過配置 FileUploadInterceptor 攔截器的參數的方式來進行限制
maximumSize (optional) - 默認的最大值為 2M. 上傳的單個文件的最大值
allowedTypes (optional) - 允許的上傳文件的類型. 多個使用 , 分割
allowedExtensions (optional) - 允許的上傳文件的擴展名. 多個使用 , 分割.
注意: 在 org.apache.struts2 下的 default.properties 中有對上傳的文件總的大小的限制. 可以使用常量的方式來修
改該限制
struts.multipart.maxSize=2097152
定制錯誤消息. 可以在國際化資源文件中定義如下的消息:
struts.messages.error.uploading - 文件上傳出錯的消息
struts.messages.error.file.too.large - 文件超過最大值的消息
struts.messages.error.content.type.not.allowed - 文件內容類型不合法的消息
struts.messages.error.file.extension.not.allowed - 文件擴展名不合法的消息
問題: 此種方式定制的消息並不完善. 可以參考 org.apache.struts2 下的 struts-messages.properties, 可以提供更多的定制信息.
2. 文件的下載:
1). Struts2 中使用 type="stream" 的 result 進行下載即可
2). 具體使用細節參看 struts-2.3.15.3-all/struts-2.3.15.3/docs/WW/docs/stream-result.html
3). 可以為 stream 的 result 設定如下參數
contentType: 結果類型 contentLength: 下載的文件的長度 contentDisposition: 設定 Content-Dispositoin 響應頭. 該響應頭指定接應是一個文件下載類型, 一般取值為
attachment;filename="document.pdf".
inputName: 指定文件輸入流的 getter 定義的那個屬性的名字. 默認為 inputStream
bufferSize: 緩存的大小. 默認為 1024 allowCaching: 是否允許使用緩存 contentCharSet: 指定下載的字符集
4). 以上參數可以在 Action 中以 getter 方法的方式提供!
3. 表單的重復提交問題
1). 什麼是表單的重復提交
> 在不刷新表單頁面的前提下:
>> 多次點擊提交按鈕
>> 已經提交成功, 按 "回退" 之後, 再點擊 "提交按鈕".
>> 在控制器響應頁面的形式為轉發情況下,若已經提交成功, 然後點擊 "刷新(F5)"
> 注意:
>> 若刷新表單頁面, 再提交表單不算重復提交
>> 若使用的是 redirect 的響應類型, 已經提交成功後, 再點擊 "刷新", 不是表單的重復提交
2). 表單重復提交的危害: 略
3). Struts2 解決表單的重復提交問題:
I. 在 s:form 中添加 s:token 子標簽
> 生成一個隱藏域
> 在 session 添加一個屬性值
> 隱藏域的值和 session 的屬性值是一致的.
II. 使用 Token 或 TokenSession 攔截器.
> 這兩個攔截器均不在默認的攔截器棧中, 所以需要手工配置一下
> 若使用 Token 攔截器, 則需要配置一個 token.valid 的 result
> 若使用 TokenSession 攔截器, 則不需要配置任何其它的 result III. Token VS TokenSession
> 都是解決表單重復提交問題的
> 使用 token 攔截器會轉到 token.valid 這個 result
> 使用 tokenSession 攔截器則還會響應那個目標頁面, 但不會執行 tokenSession 的後續攔截器. 就像什麼都沒發生過一樣!
IV. 可以使用 s:actionerror 標簽來顯示重復提交的錯誤消息. 該錯誤消息可以在國際化資源文件中覆蓋. 該消息可以在 struts-messages.properties 文件中找到
struts.messages.invalid.token=The form has already been processed or no token was supplied, please try again.
4. 自定義攔截器
具體步驟:
I. 定義一個攔截器的類
> 可以實現 Interceptor 接口
> 繼承 AbstractInterceptor 抽象類
II. 在 struts.xml 文件配置.
1 <interceptors> 2 <interceptor name="hello" 3 class="com.atguigu.struts2.interceptors.MyInterceptor"></interceptor> 4 </interceptors> 5 6 <action name="testToken" class="com.atguigu.struts2.token.app.TokenAction"> 7 <interceptor-ref name="hello"></interceptor-ref> 8 <interceptor-ref name="defaultStack"></interceptor-ref> 9 <result>/success.jsp</result> 10 <result name="invalid.token">/token-error.jsp</result> 11 </action>
III. 注意: 在自定義的攔截器中可以選擇不調用 ActionInvocation 的 invoke() 方法. 那麼後續的攔截器和 Action 方法將不會被調用. Struts 會渲染自定義攔截器 intercept 方法返回值對應的 result