序
Struts 是一個(gè) MVC 框架返咱,它的核心是?攔截器?和?值棧:
? ? ? ? a:每個(gè)攔截器實(shí)現(xiàn)單一功能亦歉,攔截器的組合實(shí)現(xiàn)了整個(gè)流程處理。
? ? ? ? b:值棧承載數(shù)據(jù)议慰,通過(guò)值棧統(tǒng)一了數(shù)據(jù)的處理邏輯蒙保,簡(jiǎn)單而且高效辕棚。
創(chuàng)建 struts 項(xiàng)目的大致順序如下:
? ? 1、創(chuàng)建工程邓厕,引入 jar 包
????????????????org.apache.struts:struts2-core:+
? ? 2逝嚎、配置 web.xml,攔截所有請(qǐng)求
? ??????????????<filter>
????????????????????<filter-name>abc</filter-name>
? ? ? ? ? ? ? ? ? ?<filter-class>StrutsPrepareAndExecuteFilter</filter-class>
? ? ? ? ? ? ? ?</filter>
? ? ? ? ? ? ? <filter-mapping>
? ????????????????<filter-name>abc</filter-name>
? ????????????????<url-partern>/*</url-partern>
? ? ? ? ? ? ?</filter-mapping>
3详恼、配置 struts.xml, 實(shí)現(xiàn) action补君,實(shí)現(xiàn) jsp (MVC)
? ??C:
? ? ??<contant name="devMode" value="true" />
? ? ? <package name="emp" namespace="/emp" extends="struts-default">
? ????????<action name="list" class="EmpAction" method="emplist">
? ? ????????????<result name="success" type="dispatcher">/emplist.jsp</result>
? ????????</action>
????????</package>
? ??M:
? ??????public class EmpAction extends ActionSpport {
? ? ????????????private String name;
? ? ????????????private List<Emp> emps = new ArrayList<>();
? ? ????????????public String emplist() {
? ? ? ? ????????emps = empDAO.findByName(name);
? ? ? ? ????????return SUCCESS;
? ? ? ? ? ? ? ? ?}
????????????}
? ? ? ?V:
? ??????<ul>
? ????????????<s:iterator value="emps" var="e" status="s">
? ? ? ? ? ? ? ?<li>${s.index}: ${e.name} | ${e.salary}</li>
? ????????????</s:iterator>
????????</ul>
4、部署昧互,啟動(dòng)挽铁,看效果。
流程
1敞掘、請(qǐng)求發(fā)送給 StrutsPrepareAndExecuteFilter
2屿储、StrutsPrepareAndExecuteFilter 詢問(wèn) ActionMapper:請(qǐng)求是否 Action(是的話返回非空的 ActionMapping)
3、如果上一步確定請(qǐng)求時(shí) Action渐逃,則把請(qǐng)求交給 ActionProxy 處理够掠。 ActionProxy 是 xwork 和 struts 的連接層。
4茄菊、ActionProxy 通過(guò) ConfigurationManager 加載配置文件疯潭,確定相關(guān) Action 類和方法。
5面殖、ActionProxy 創(chuàng)建一個(gè) ActionInvocation 實(shí)例并初始化竖哩。
6、ActionInvocation 負(fù)責(zé)調(diào)用 Action脊僚,在調(diào)用的前后相叁,需要執(zhí)行 Interceptor 鏈(默認(rèn)是?defaultStack)遵绰。在調(diào)用完 Action 后要執(zhí)行 result 的結(jié)果。
7增淹、把結(jié)果發(fā)送到客戶端椿访。
defaultStack
exception?- 異常處理,包在最外面
servletConfig?- 處理 xxxAware 接口
i18n?- 處理國(guó)際化
prepare?- 如果實(shí)現(xiàn)了 preparable 接口虑润,則尋找并執(zhí)行 pepareXxx/repareDoXxx
chain?- 如果 type 是 chain 則復(fù)制值棧
scopedModelDriven?- 在 request/session 范圍內(nèi)查找并初始化 model
modelDriven?- 調(diào)用 getModel 方法成玫,初始化 model 并壓棧
fileUpload?- 處理文件上傳(MultiPartRequestWrapper 請(qǐng)求)
checkbox?- 將隱藏域的 checkbox 賦值為 false
datetime?- 格式化 text 域中的時(shí)間
multiselect?- 為 __multiselect_ 賦值 null
staticParams?- 把配置中的靜態(tài)參數(shù)填裝到 Action 中
actionMappingParams?- 把 actionMapping 里的參數(shù)壓棧
params?- 封裝請(qǐng)求參數(shù)到值棧
conversionError?- 處理轉(zhuǎn)型錯(cuò)誤
validation?- 進(jìn)行編程驗(yàn)證
workflow?- 處理驗(yàn)證錯(cuò)誤,跳轉(zhuǎn) input 頁(yè)面
debugging?- 處理 devMode 等
StrutsPrepareAndExecuteFilter
如果需要用到其他過(guò)濾器拳喻,為了不影響 Struts 功能哭当,需要把 StrutsPrepareAndExecuteFilter 拆分,再將自己的過(guò)濾器插入中間:
????????????StrutsPrepareAndExecuteFilter = StrutsPrepareFilter + StrutsExecuteFilter
比如冗澈,如果使用 SiteMesh 進(jìn)行頁(yè)面裝飾:
? ??????<filter-mapping>
? ? ? ? ? ? ? ?<filter-name>StrutsPrepareFilter</filter-name>
? ???????????? <url-pattern>/*</url-pattern>
????????</filter-mapping>
????????<filter-mapping>
? ???????????? <filter-name>sitemesh</filter-name>
? ???????????? <url-pattern>/*</url-pattern>
????????</filter-mapping>
????????<filter-mapping>
? ???????????????? <filter-name>StrutsExecuteFilter</filter-name>
? ???????????????? <url-pattern>/*</url-pattern>
? ? ? ? ? </filter-mapping>
值棧
值棧是 struts 中數(shù)據(jù)傳遞處理的核心钦勘,它的基礎(chǔ)是 OGNL,是一種 EL 表達(dá)式亚亲。
OGNL
OGNL 是 Struts2 中值棧的基礎(chǔ):
? ? 1个盆、三要素: Expression, Root, Context.
? ? 2、核心: getValue.. setValue..
As:
// Prepare Data
Person p1 = new Person("sharry");
Person p2 = new Person("shatom");
Person p3 = new Person("shahat");
/* Get Value from Single Object */
String name = Ognl.getValue("name", p1);
String name = Ognl.getValue("name", p1, String.class);
/* Multiple Objects, with a Map Container */
Map<String, Object> context = new HashMap<>();
context.put("req", p1);
context.put("ses", p2);
context.put("app", p3);
// expression
Ognl.getValue("name", p2);
Ognl.getValue("name", context, p2);
Ognl.getValue("#app.name", context, p2);
/* Map -> OgnlContext */
OgnlContext context = new OgnlContext();
context.put("req", p1);
context.put("ses", p2);
context.put("app", p3);
context.setRoot(p1);
// params: [expression, context, root]
Ognl.getValue("name.length()", context, context.getRoot());? ? ? ? ? // default, from root
Ognl.getValue("#app.name.toUpperCase()", context, context.getRoot()); // from #app
Ognl.getValue("@java.lang.Math@E", context, context.getRoot());? ? ? // static method invoke.
// with '$()' method, anything can be easier:
public Object $(String exp) { return Ognl.getValue(exp, context, context.getRoot()); }
$("name");
$("#ses.name");
$("#app.name.toUpperCase()");
$("@java.lang.Math@E");
/* Operate on Collection */
// Make list/map
$("{111, 222, 333, 444}");
$("#{aaa: aaa, bbb: bbb}");
// Get Value
$("#tom.address['city']");
// 投影集合:collection.{expression}
$("friends.{name}");
// 過(guò)濾集合:collection.{?/^/$ expression}
$("friends.{? #name.length() > 7}");
ActionConext/ValueStack
每次 action 調(diào)用都會(huì)創(chuàng)建一個(gè)運(yùn)行環(huán)境 ActionContext朵栖。它保存在 ThreadLocal 中,線程安全柴梆。
ActionContext 的主體是一個(gè) Map 結(jié)構(gòu):
publicclassActionContext{
????????????????privateMapcontext;
}
在預(yù)處理過(guò)程陨溅,=ActionContext#context= 里會(huì)被放入 request/session/application/ValueStack/etc,它本質(zhì)是個(gè)以?ValueStack?為 root 的 =OgnlContext=绍在,是 struts 運(yùn)行過(guò)程中的數(shù)據(jù)中心门扇。
OgnlContext (ActionContext#context)
? +--- attr
? +--- request
? +--- CompoundRoot (ValueStack, ArryList with pop/push)
? +--- session
? +--- others
CompoundRoot(ValueStack) 是個(gè)堆棧結(jié)構(gòu),最先被壓入的是 Action 的實(shí)例偿渡,實(shí)例屬性將會(huì)在后面的 params 攔截器中被賦值臼寄。它的物理位置是:
????????request.getAttribute("struts.valueStack")
在 jsp 中,可以通過(guò) struts 提供的標(biāo)簽使用值棧中的數(shù)據(jù):
????????<property value="salary" />
????????<iterator value="emps" var="e" status="s">...</iterator>
:
????????${salary} // 因?yàn)?struts 重寫(xiě)了 Request#getAttribute 方法溜宽,所以 ${salary} 會(huì)先從 request 里取吉拳,取不到再去值棧中取
:
????????<property value="#session.cart" /> // 非 root 內(nèi)的數(shù)據(jù)的獲取
:
????????<property value="emp.salary" /> // 屬性的屬性
????????<property value="emp['salary']" />
:
????????<s:property value="message" /> // 輸出第一個(gè)擁有 message 屬性對(duì)象的屬性值
????????<s:property value="[2].message" />? // 從第二個(gè)開(kāi)始搜索
ServletContext
繼承自 ActionContext, 擴(kuò)展了獲取處理 Servlet 原生對(duì)象的一些方法。
取得HttpSession對(duì)象:
? ? ? ? ? ?HttpSession session = ServletActionContext. getRequest().getSession();
參數(shù)封裝
請(qǐng)求參數(shù)
client
?<!-- 1. to Property -->
<s:form action="empsave" method="post">
? ????<s:textfield name="ename1" label="Employee Name" />
? ????<s:select name="deptno1" list="depts" label="Department" />
? ????<s:submit />
</s:form>
<!-- 2. to Model -->
<s:form action="empsave" method="post">
? ????<s:textfield name="emp.name" label="Employee Name" />
? ????<s:select name="emp.dept.deptno" list="depts" label="Department" />
? ????<s:submit />
</s:form>
<!-- 3. i18n -->
<s:form action="empsave" method="post">
? ????<s:textfield key="ename2" />
? ????<s:select key="deptno2" list="depts" />
? ????<s:submit />
</s:form>
<!-- 4. ModelDriven -->
server
// for 1
String ename1;
Long deptno1;
public String empsave() {
? ? ????Emp e = new Emp(ename1, new Dept(deptno1));
? ? ????empDAO.save(e);
? ? ????return SUCCESS;
}
// for 2
Emp emp;
public String empsave() {
? ? ????empDAO.save(emp);
? ? ????return SUCCESS;
}
ModelDriven:
如果要把請(qǐng)求的參數(shù)封裝到 Model 類中适揉,最好使用 =ModelDriven=留攒。只需要繼承并實(shí)現(xiàn) ModelDriven 接口即可:
public EmpAction implements ModelDriven<Emp> {
? ? ????private Emp emp = new Emp();? // 可被各個(gè) action 復(fù)用
? ? ????@Override Emp getModel() {
? ? ? ? return emp;
? ? }
? ? public String empsave() {
? ? ? ? empDAO.save(emp);
? ? }
? ? public String empdel() {
? ? ? ? empDAO.delete(emp.getId());
? ? }
}
[補(bǔ)充內(nèi)容]
*比較特殊的是 update 操作*,參數(shù)的封裝邏輯應(yīng)該分為兩步:
? ? 1嫉嘀、先根據(jù)參數(shù)中的 id 從數(shù)據(jù)庫(kù)中讀取實(shí)體類
? ? 2炼邀、再將其他請(qǐng)求參數(shù)覆蓋到實(shí)體類
使用 ModelDriven 方式,我們需要這樣定義 getModel 方法:
private Emp emp;
// 經(jīng)過(guò) ModelDriven 攔截器時(shí)從數(shù)據(jù)庫(kù)中加載完整 emp
// 之后經(jīng)過(guò) Params 攔截器剪侮,再將請(qǐng)求參數(shù)覆蓋其中
@Override Emp getModel() {
? ? ????emp = empDAO.findById(emp.getId());
? ? ????return emp;
}
可以看到拭宁,我們需要在 ModelDriven 攔截器前后分別執(zhí)行一次 Params 攔截器,一次用于獲取 Id,一次用于覆蓋數(shù)據(jù)杰标。 這就必須使用?paramsPrepareParamsStack?攔截器棧兵怯。
上述 getModel 定義會(huì)作用于所有 Action 請(qǐng)求,但對(duì) save/delete 等請(qǐng)求是沒(méi)必要的在旱,因?yàn)樗麄儾恍枰獜臄?shù)據(jù)庫(kù)中再加載一次 emp摇零。 所以需要區(qū)分,只為特定 action 請(qǐng)求加載 emp桶蝎。這就需要用到?Prepare?攔截器驻仅。 使用?paramsPrepareParamsStack?+?Prepare?后,整個(gè)執(zhí)行順序?yàn)椋????????
????????<params> -> prepareDo -> prepare -> getModel -> <params> -> action
All in All:
// 需要先配置使用 paramsPrepareParamsStack
// 再讓 Action 實(shí)現(xiàn) Prepareable 接口
private Emp emp;
void prepareUpdate() {
? ? emp = empDAO.findById(emp.getId());
}
Emp getModel() {
? ? if(emp == null)
? ? ? ? emp = new Emp();
? ? return emp;
}
當(dāng)然登渣,有時(shí)侯也沒(méi)必要這么麻煩噪服,為 update 請(qǐng)求多定義幾個(gè)接收 property,再手動(dòng)加載胜茧,手動(dòng)賦值粘优。即可。
響應(yīng)數(shù)據(jù)
跟請(qǐng)求參數(shù)的處理是一致的呻顽,都是在 Action 中定義雹顺,隨著 action 被壓入值棧,就可以在 jsp 中使用能從值棧中獲取數(shù)據(jù)的標(biāo)簽去獲取并渲染數(shù)據(jù)了廊遍。
當(dāng)然嬉愧,也可以將數(shù)據(jù)放到 request/session 中。
獲取 Request/Response 的方式有:
????ActionContext.getContext().getSession();
????ServletActionContext.getRequest();
????implements xxxAware
類型轉(zhuǎn)換
html 提交的數(shù)據(jù)全都是字符串類型喉前,所以在 server 端要轉(zhuǎn)換為合適的 Java 類型
? ? ?1没酣、struts 中,由 Parameters 攔截器負(fù)責(zé)轉(zhuǎn)換卵迂,它是 defaultStack 中的一員
? ? ?2裕便、Parameters 攔截器只能對(duì)?字符串->基本類型?進(jìn)行轉(zhuǎn)換。復(fù)雜轉(zhuǎn)換需要自定義轉(zhuǎn)換器:
? ? ? ? ? ? ? ? a:創(chuàng)建轉(zhuǎn)換器见咒,即實(shí)現(xiàn) ognl.TypeConverter 接口偿衰。實(shí)際上繼承 StrutsTypeConverter 即可。
? ? ? ? ? ? ? ? b:配置使用改览“タ眩基于字段(model/ModelClassName-conversion.properties)或基于類型(src/xwork-conversion.properties),添加:
? ??????????????????????????java.util.Date=imfine.convert.DataConverter
? ?3恃疯、 如果轉(zhuǎn)換失敗漏设,由 ConversionError 攔截器負(fù)責(zé)添加出錯(cuò)消息。
? ? 4今妄、如果存在轉(zhuǎn)換或驗(yàn)證錯(cuò)誤郑口,由 Workflow 攔截器決定是否轉(zhuǎn)到名為 input 的 result
? ? 5鸳碧、可添加?ActionName.properties#invalid.filedvalue.fieldName=xxx?定制錯(cuò)誤信息。
? ? 6犬性、頁(yè)面上中瞻离,錯(cuò)誤信息可以通過(guò)下面方式顯示:
? ??????????${fieldErrors.age[0] }
????????????<s:fieldError fieldName="age" />? // 默認(rèn)主題會(huì)生成 ul 列表
輸入驗(yàn)證
驗(yàn)證是由 ValidationInterceptor 攔截器實(shí)現(xiàn)的。
驗(yàn)證分為兩種:
? ? 1乒裆、聲明式驗(yàn)證套利,需要在action類的包下面創(chuàng)建一個(gè)驗(yàn)證使用的 xml 文件,里面定義我們要驗(yàn)證的內(nèi)容鹤耍。 ??
? ? 2肉迫、編程式驗(yàn)證,為 Action 類實(shí)現(xiàn) Validatable 接口稿黄,然后喊衫,實(shí)現(xiàn) validate 方法。
聲明式驗(yàn)證
比如杆怕,要為 LoginAction 做驗(yàn)證族购,需要在相同目錄下面新建一個(gè) LoginAction-validation.xml,內(nèi)容類似下面:
? ??<?xml version="1.0" encoding="UTF-8"?>
????<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.dtd">
<validators>
? <!-- 驗(yàn)證我們的字段 -->
? <field name="username">
? ? <field-validator type="requiredstring">
? ? ? <param name="trim">true</param>
? ? ? <message>請(qǐng)?zhí)顚?xiě)您的用戶名</message>
? ? </field-validator>
? </field>
? <field name="password">
? ? <field-validator type="requiredstring">
? ? ? <param name="trim">true</param>
? ? ? <message>請(qǐng)?zhí)顚?xiě)您的密碼</message>
? ? </field-validator>
? </field>
</validators>
內(nèi)建的驗(yàn)證有 15 中陵珍,可以參加文檔寝杖。例:
1、required
2互纯、requiredstring
3瑟幕、stringlength
4、email
5伟姐、url
6、regex
7亿卤、int
8愤兵、conversion
9、expression/fieldexpression
例如排吴,要驗(yàn)證數(shù)字范圍:
<!-- 字段驗(yàn)證秆乳,IntRange驗(yàn)證器 -->
<field-validator type="int">
? <param name="min">20</param>
? <param name="min">20</param>
? <message key="error.int" />
</field-validator>
如果要驗(yàn)證兩次輸入的密碼是否不一致
<!-- 測(cè)試非字段驗(yàn)證 -->
<validator type="expression">
? <param name="expression"><![CDATA[password1=password2]]></param>
? <message> 兩次輸入的密碼不一致,請(qǐng)重試钻哩。 </message>
</validator>
編程式驗(yàn)證
首先屹堰, Action 要實(shí)現(xiàn) Validateable 接口。當(dāng)然街氢,ActionSupport 類是實(shí)現(xiàn)了這個(gè)接口的扯键,所以如果我們也可以直接繼承 ActionSupport 類。
其次珊肃,我們需要實(shí)現(xiàn) validate() 方法荣刑。如果針對(duì)特定方法進(jìn)行驗(yàn)證馅笙,我們需要實(shí)現(xiàn)相關(guān)的 validateMethodName() 方法。下面是一個(gè)栗子厉亏,對(duì)登錄進(jìn)行驗(yàn)證董习。要求
? ? 1、用戶名不為空
????2爱只、密碼不為空
/**
* 驗(yàn)證登錄輸入
*/
public void validateLogin() {
? ? if (username == null || username.isEmpty())
? ? ? ? addFieldError("username", "請(qǐng)?zhí)顚?xiě)用戶名");
? ? if (password == null || password.isEmpty())
? ? ? ? addFieldError("password", "請(qǐng)?zhí)顚?xiě)密碼");
}
最后皿淋,我們要為 action 寫(xiě)一個(gè)名字為 input 的 result。即如果驗(yàn)證失敗后恬试,顯示哪個(gè)頁(yè)面窝趣。如果不寫(xiě) input,會(huì)拋出異常忘渔。
異常處理
聲明式異常處理
????<exception-mapping result="input" exception="xxxException" />
也可以通過(guò) global-exception-mappings 設(shè)置全局異常處理高帖。
聲明式異常處理由攔截器 ExceptionMappingInterceptor 處理。當(dāng)出現(xiàn)異常時(shí)畦粮, ExceptionMappingInterceptor 會(huì)向 ValueStack 中添加兩個(gè)對(duì)象:
????????exception 表示被捕獲異常的 Exception 對(duì)象
????????exceptionStack 包含著被捕獲異常的棧
所以可以通過(guò) <s:property /> 來(lái)顯示異常信息散址。通過(guò)查看 ExceptionMappingInterceptor 源碼,一清二楚宣赔。
在頁(yè)面上顯示:
????????<s:actionErrors />
????????<p> ${actionErrors[0]} </p>
可以在 head 標(biāo)簽里使用 <s:header /> 生成一些內(nèi)置的錯(cuò)誤樣式预麸。
渲染視圖
Result type:
dispatcher, 轉(zhuǎn)發(fā)到 jsp/html,默認(rèn)類型
chain儒将,轉(zhuǎn)發(fā)到另一個(gè) action
redirect, 重定向到 jsp/html
redirectAction, 重定向到另一個(gè) action
plainText吏祸,返回文件內(nèi)容,text/plain
freemarker/velocity, 轉(zhuǎn)發(fā)到 freemarker/velocity 視圖
stream, 處理二進(jìn)制數(shù)據(jù)钩蚊,比如上傳下載贡翘,還可以處理 JSON 返回
json, 將對(duì)象序列化為 json 字符串并返回,需要 struts-json-plugin.jar 支持
i18n
處理國(guó)際化的是 i18n 攔截器砰逻。
使用資源文件的方式有:
????<s:text />
????<s:i18n />
????標(biāo)簽里的 key 屬性
????驗(yàn)證文件中的 <message key=”xxx”>
????Action 中的 getText() 方法
比如:
<s:textfield label="xxx" /> // xxx 按照原樣輸出
<s:property value="yyy" />? ? // yyy 是值棧中對(duì)應(yīng)名字的數(shù)據(jù)
<s:text name="zzz" />? ? ? ? ? // zzz 表示從系統(tǒng)的資源文件(xxx.properties)加載數(shù)據(jù)
<s:textfield key="xyz" />? ? ? // xyz 使用資源文件里的數(shù)據(jù)
struts 是按照下面順序判斷區(qū)域的:
? ? 1鸣驱、getParameter(“request_locale”)
????2、session.getAttribute(“WW_TRANS_I18N”)
? ? 3蝠咆、如果以上都沒(méi)有取到的話踊东,那么從系統(tǒng)中獲取 (java.util.Locale.getDefault())
資源文件的搜索順序:
????1、ActionClass.properties
? ? 2刚操、Interface.properties (every interface and sub-interface)
? ? 3闸翅、BaseClass.properties (all the way to Object.properties)
? ? 4、ModelDriven’s model (if implements ModelDriven), for the model object repeat from 1
? ? 5菊霜、package.properties (of the directory where class is located and every parent directory all the way to the root directory)
? ? 6坚冀、search up the i18n message key hierarchy itself
? ? 7、global resource properties
????????????????<constant name="struts.custom.i18n.resources" value="global" />
標(biāo)簽
property/date
property 是最基本的標(biāo)簽鉴逞,用來(lái)輸出 ValueStack 中屬性值遗菠,date 用來(lái)格式化日期
<s:property value="#sesseion.date" />
${#session.date}
<s:date name="#session.date" format="yyyy-MM-dd hh:mm:ss" />
url/param
url 用來(lái)創(chuàng)建一個(gè) url 字符串联喘,可以自動(dòng)添加 ContextPath
<s:url value="/testUrl" var="url"><s:param name="id" value="name" /></s:url> // name.值棧中的屬性
<s:url value="/testUrl" var="url"><s:param name="id" value="'name'" /></s:url>? // 'name'.字符串name
<s:url action="testAction" method="save" var="url" />? ? // 生成的是action請(qǐng)求
:
<a href="${url}">使用 s:url 定義的 url</a>
param 用于給它的父標(biāo)簽傳遞參數(shù)
默認(rèn)會(huì)對(duì) value 進(jìn)行 ognl 求值
如果想使用字面字符串,用單引號(hào)括起來(lái)
也可以不使用 value 屬性辙纬,而把值寫(xiě)在標(biāo)簽里面豁遭。這樣可以傳遞一個(gè) El 表達(dá)式的值。
set/push
set 用來(lái)向 page/request/session/application 中壓入值
<s:set name="price" value="price" scope="request" />
<div>價(jià)格: ${requestScope.price}</div>
push 用來(lái)臨時(shí)將某些值壓到 ValueStack 頂部贺拣,便于操作
<s:push value="#request.hello">
? ? 姓名: ${name}? ?
</s:push>
if/elif/else
<s:if test="price > 1000">高檔</s:if>
<s:elseif test="price > 500">中檔</s:elseif>
<s:else>低端</s:else>
iterator/sort
iterator 遍歷集合蓖谢,把可遍歷對(duì)象的每個(gè)元素依次壓入彈出值棧
<s:iterator value="#request.persons" status="s">
? ? <div>${s.index}: ${name}? -? ${age}</div>
</s:iterator>
sort 對(duì)可遍歷對(duì)象的元素排序
form/textfield/select/checkbox/radio
form 結(jié)合其他可以自動(dòng)排版,自動(dòng)回顯譬涡。
radio/select/checkboxlist 等標(biāo)簽需要使用?list?屬性提供數(shù)據(jù)闪幽。
<s:radio name="genda" list="#{'1':'Male', '0':'Female'}" label="性別" />
<!-- 服務(wù)端需要使用集合類型 -->
<s:select name="age" list="{11,12,13,14,15}" headerKey="" headerValue="請(qǐng)選擇" label="年齡">
? ? <s:optgroup label="21-30" list="#{21:21,22:22 }" />
? ? <s:optgroup label="30-40" list="#{31:31,32:32 }" />
</s:select>
<s:checkboxlist name="cities" list="#request.cities" listKey="cityId" listValue="cityName" label="城市" />
攔截器
實(shí)現(xiàn)了 Interceptor 接口的類,叫攔截器涡匀。
在 Struts 中盯腌,是利用攔截器進(jìn)行功能實(shí)現(xiàn)的,比如值的自動(dòng)封裝陨瘩,類型的轉(zhuǎn)換腕够,值棧的維護(hù),驗(yàn)證舌劳,國(guó)際化等方面帚湘。
每一個(gè)攔截器都實(shí)現(xiàn)用來(lái)完成單一的功能。多個(gè)攔截器甚淡,按照順序放在一個(gè)列表中大诸,按照順序執(zhí)行,可以達(dá)到完成一系列功能的目的贯卦。 這多個(gè)的攔截器放在一起资柔,像一根鏈條一樣,稱為攔截器棧(棧是一種非衬旄睿基本的數(shù)據(jù)結(jié)構(gòu)贿堰,它遵守先進(jìn)后出的原則。簡(jiǎn)單理解睁枕,它是有順序的一個(gè)鏈表)官边。
比如沸手,在 struts 中外遇,對(duì)值進(jìn)行自動(dòng)封裝的攔截器叫 ParameterFilterInterceptor;對(duì)異常處理的攔截器叫 ExceptionMappingInterceptor契吉;FileUploadInterceptor 負(fù)責(zé)處理文件的上傳等跳仿。其他功能,在 struts 中都有相應(yīng)的攔截器實(shí)現(xiàn)捐晶。
所以一個(gè)請(qǐng)求在到達(dá) Action 對(duì)象前會(huì)經(jīng)過(guò)一系列的攔截器菲语。
????????攔截器a -> 攔截器 B -> 攔截器 C -> 攔截器 D ... -> Action.Method -> 后續(xù)的一些處理妄辩,包括返回顯示頁(yè)面等。
在 struts.xml 中山上,可以通過(guò) interceptor-ref 為每個(gè) action 設(shè)置相應(yīng)的攔截器鏈眼耀。如果我們不去設(shè)置,那么如果我們繼承了 struts-default佩憾, action 會(huì)默認(rèn)使用 defaultStack 攔截器棧哮伟。
defaultStack 攔截器棧定義了一組有序的攔截器,它包含的每個(gè)攔截器都是 struts 內(nèi)置的妄帘。我們可以通過(guò)在 struts-default.xml 中查看詳情楞黄。
配置攔截器的方式為,在 action 下面抡驼,增加 interceptor-ref 節(jié)點(diǎn):
<action name="xxx" class="yyy.ZzzAction" method="xxx">
? <result>/aaa.jsp</result>
? <interceptor-ref name="A攔截器" />
? <interceptor-ref name="B攔截器" />
</action>
在 struts 處理請(qǐng)求的過(guò)程中鬼廓,會(huì)分析你的配置,把你為 action 配置的所有攔截器引用按照先后順序整理成一個(gè)新的鏈表致盟。然后按照順序去執(zhí)行碎税。
當(dāng)然,你也可以在 package 里面聲明新的攔截器和攔截器棧勾邦。上面的代碼可以改寫(xiě)蚣录,并改進(jìn)為:
<!-- 攔截器的聲明 -->
<interceptors>
? <!-- 下面聲明是我們自己實(shí)現(xiàn)的兩個(gè)攔截器 -->
? <interceptor name="A攔截器" class="xxx.AI" />
? <interceptor name="B攔截器" class="xxx.BI" />
? <!-- 這里定義的是一個(gè)攔截器棧,就是把一系列的攔截器放在一起起個(gè)名字眷篇,方便在 action 中使用 -->
? <!-- 注意萎河, interceptor-ref 的先后順序不同,效果是不一樣的蕉饼。 -->
? <interceptor-stack name="我的攔截器">
? ? <interceptor-ref name="A攔截器" />
? ? <interceptor-ref name="B攔截器" />
? ? <!-- 在使用自定義攔截器的時(shí)候虐杯,一定要注意,如果不寫(xiě)下面的一部昧港,將會(huì)用我們自己的攔截器把 struts 自己的攔截器給覆蓋掉擎椰。這樣會(huì)導(dǎo)致struts完成不了一些事情。 -->
? ? <!-- 所以创肥,在我們聲明的這個(gè)攔截器棧中达舒,把 defaultStack 放在里面 -->
? ? <interceptor-ref name="defaultStack" />
? </interceptor-stack>
</interceptors>
<!-- 在 action 中使用我們聲明的攔截器 -->
<action name="xxx" class="yyy.ZzzAction" method="xxx">
? <result>/aaa.jsp</result>
? <interceptor-ref name="我的攔截器" />
</action>
<!-- 上面的一段,跟下面的定義是相同的效果 -->
<!--
? ? <action name="xxx" class="yyy.ZzzAction" method="xxx">
? ? ? <result>/aaa.jsp</result>
? ? ? <interceptor-ref name="A攔截器" />
? ? ? <interceptor-ref name="B攔截器" />
? ? ? <interceptor-ref name="defaultStack" />
? ? </action>
-->
當(dāng)然叹侄,如果想自定義攔截器巩搏,只需要實(shí)現(xiàn) Interceptor 接口即可。為了方便趾代,也可以直接繼承 AbstractInterceptor 類贯底,這樣,我們只需要重寫(xiě) intercept 方法就可以了撒强。
例子:
第一步禽捆,實(shí)現(xiàn)自己的攔截器笙什。
/**
* 這是一個(gè)簡(jiǎn)單的用來(lái)判斷登錄的攔截器栗子。
*/
public class VertifyInterceptor extends AbstractInterceptor {
? ? // 日志系統(tǒng)胚想,你們需要了解一下
? ? Log logger = LogFactory.getLog(VertifyInterceptor.class);
? ? @Override
? ? public String intercept(ActionInvocation invocation) throws Exception {
? ? ? ? // 獲取 action 的名字
? ? ? ? String actionName = invocation.getProxy().getActionName();
? ? ? ? // 這只是一個(gè)小例子琐凭,通過(guò)這樣設(shè)置,我們可以讓這些請(qǐng)求跳過(guò)下面的驗(yàn)證浊服。
? ? ? ? Set<String> excludes = new HashSet<>();
? ? ? ? excludes.add("login");
? ? ? ? excludes.add("index");
? ? ? ? if (excludes.contains(actionName)) {
? ? ? ? ? ? // 如果請(qǐng)求在我們的白名單中淘正,將不執(zhí)行之后的判斷邏輯。
? ? ? ? ? ? return invocation.invoke();
? ? ? ? }
? ? ? ? // 下面開(kāi)始進(jìn)行相關(guān)驗(yàn)證臼闻。
? ? ? ? HttpSession session = ServletActionContext.getRequest().getSession();
? ? ? ? // 未登錄檢測(cè)鸿吆。如果session為空,或者 session 沒(méi)有保存相關(guān)狀態(tài)述呐,則判斷惩淳,這個(gè)人沒(méi)有登錄稚补。那么讓他去登錄頁(yè)面提陶。
? ? ? ? if (session == null || session.getAttribute(Globals.USER_KEY) == null) {
? ? ? ? ? ? // 記錄或打印日志
? ? ? ? ? ? logger.info(ServletActionContext.getRequest().getRequestURI() + "? 尚未登錄惧互,返回首頁(yè)");
? ? ? ? ? ? // 如果直接返回一個(gè)字符的話为流,那么下一步將直接進(jìn)入這里指定的 index 頁(yè)面,而不會(huì)執(zhí)行到 action 里面去岂傲。
? ? ? ? ? ? return "loginPage";
? ? ? ? }
? ? ? ? // 權(quán)限控制扫沼。防止繞過(guò)驗(yàn)證叁温,直接進(jìn)入管理員的頁(yè)面江掩。
? ? ? ? // 如果請(qǐng)求的 namespace 是 /admin学辱,但 session 里保存的用戶類型不是 1,那么我們可以判斷环形,這是非管理員要訪問(wèn)我們的管理員頁(yè)面策泣。所以毫無(wú)疑問(wèn),要禁止他的操作抬吟。
? ? ? ? if (invocation.getProxy().getNamespace().equalsIgnoreCase("/admin") && ((User) session.getAttribute(Globals.USER_KEY)).getUsertypeid() != 1) {
? ? ? ? ? ? logger.info(ServletActionContext.getRequest().getRequestURI() + "? 不具備相應(yīng)權(quán)限萨咕,返回登錄");
? ? ? ? ? ? // 將 session 設(shè)置為無(wú)效
? ? ? ? ? ? session.invalidate();
? ? ? ? ? ? // 返回相關(guān)警告頁(yè)面,或者跳轉(zhuǎn)到登錄頁(yè)面
? ? ? ? ? ? return "errorPage";
? ? ? ? }
? ? ? ? // 默認(rèn)情況火本,繼續(xù)運(yùn)行危队。
? ? ? ? return invocation.invoke();
? ? }
}
第二步,在 struts.xml 中配置自定義的攔截器
<interceptors>
? <interceptor name="vertify" class="xxx.interceptor.VertifyInterceptor" />
? <interceptor-stack name="myVertifyStack">
? ? <interceptor-ref name="vertify" />? ? ? ? ? ? ? <!-- 注意钙畔,把我們的驗(yàn)證放在第一個(gè)位置茫陆,那么如果驗(yàn)證失敗,將不執(zhí)行下面的攔截器刃鳄,會(huì)節(jié)約一些資源盅弛。 -->
? ? <interceptor-ref name="defaultStack" />
? </interceptor-stack>
</interceptors>
<!-- 在 action 中使用我們聲明的攔截器 -->
<action name="listAll" class="xxx.action.EmpAction" method="listAll">
? <result>/aaa.jsp</result>
? <interceptor-ref name="myVertifyStack" />
</action>
<!--
當(dāng)前钱骂,除了像上面一樣叔锐,給每個(gè) action 添加 interceptor-ref挪鹏,我們也可以通過(guò)一下語(yǔ)句,為整個(gè)包下的 action 設(shè)置默認(rèn)的 interceptor愉烙,如下:
<default-interceptor-ref name="myVertifyStack" />
-->
就這么簡(jiǎn)單讨盒。
Ajax/Json
在 struts2 中使用 ajax 獲取 json 數(shù)據(jù)主要以下三種方法:
Servlet 原生寫(xiě)法
struts.xml:
<package name="a" extends="struts-default">
? <action name="einfo" class="EmpAction" method="einfo">
? ? <!-- 不需要 result -->
? </action>
</package>
action:
public String einfo () throws Exception {
? ? // Writer
? ? PrintWriter writer = ServletActionContext.getResponse().getWriter();
? ? // Data
? ? String result = "{\"name\": \"Alice\", \"age\": 22}";
? ? // Output
? ? writer.write(result);
? ? writer.flush();
? ? // Return
? ? return null;
}
front-page:
$.post("/einfo.action",null,r=>alert(r));
使用 stream 類型
struts.xml:
<package name="a" extends="struts-default">
? <action name="einfo" class="EmpAction" method="einfo">
? ? <result type="stream">
? ? ? <!-- optional -->
? ? ? <param name="contentType">text/html; charset=UTF-8</param>
? ? ? <param name="inputName">inputStream</param>
? ? </result>
? </action>
</package>
action:
// Define
private InputStream inputStream;
public String einfo () {
? ? // Data
? ? String result = "{\"name\": \"Alice\", \"age\": 22}";
? ? // Assign
? ? inputStream = new ByteArrayInputStream(result.getBytes("UTF-8"));
? ? return "success";
}
front-page:
$.post("/einfo.action",null,r=>console.log(r));
使用 struts-json 插件
json 插件會(huì)自動(dòng)將指定對(duì)象序列化為 json 字符串并返回。
首先添加依賴:
compile "org.apache.struts:struts2-json-plugin:+"
struts.xml:
<package name="a" extends="json-default">
? <action name="elist" class="EmpAction" method="elist">
? ? <!-- 默認(rèn)情況步责,序列化值棧最頂端的對(duì)象 -->
? ? <result name="r1" type="json"></result>
? ? <!-- 通過(guò) root 指定要序列化的對(duì)象 (OGNL 表達(dá)式) -->
? ? <result name="r2" type="json">
? ? ? <param name="root">#request.emps</param>
? ? </result>
? ? <!-- 使用 includeProperties/excludeProperties 過(guò)濾要序列化的字段 -->
? ? <result name="r3" type="json">
? ? ? <param name="excludeProperties">\[\d+\].department, \[\d+\].manager</param>
? ? </result>
? ? <!-- 使用 OGNL 的投影集合功能返顺,定制序列化的字段 -->
? ? <result name="r4" type="json">
? ? ? <param name="root">#request.emps.{#{"n": name, "s": salary}}</param>
? ? </result>
? </action>
</package>
action:
public String elist () {
? ? request.put("emps", empDAO.getAll());
? ? return "r3";
}
front-page:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
? ? if(xhr.readyState === 4) {
? ? ? ? const emps = JSON.parse(xhr.responseText);
? ? ? ? document.querySelector("#xxx").innerHTML = emps.map(e => {
? ? ? ? ? ? `<tr><td>${e.n}</td>${e.s}<td></td></tr>`
? ? ? ? }).join("\n");
? ? }
};
xhr.open("GET", "/elist.action", true);
xhr.send();
Files
upload
form:
<s:form action="upload" enctype="multipart/form-data">
? <s:file name="aaa" label="選擇" />
? <s:textfield name="describe" label="描述" />
? <s:submit value="保存"></s:submit>
</s:form>
action:
private File aaa;
private String aaaFileName;
private String aaaContentType;
private String describe;
public String upload () {
? ? // Save with Stream
? ? FileUtils.copyFile(aaa, new File("d:/xxx/" + aaaFileName));
? ? return "success";
}
需要注意:
struts2 的文件上傳實(shí)際上用的是?Commons FileUpload?組件,所以要導(dǎo)入相關(guān) jar 包
處理文件上傳的是 FileUpload 攔截器蔓肯∷烊担可通過(guò)配置攔截器參數(shù)(maximumSize/allowedExtensions)限制上傳文件的大小、格式等
在 Action 中定義上述 3 個(gè)屬性(param+XXX)蔗包,配合 IO 流完成數(shù)據(jù)寫(xiě)入秉扑。多文件上傳則需要將上述屬性定義成 List 類型
上面的三個(gè)屬性可以隨意定義,但是相應(yīng)的 setter 方法一定是 paramXXX 格式
download
超鏈接的形式是靜態(tài)文件下載调限。但如果要?jiǎng)討B(tài)下載舟陆,需要使用 type=stream。
struts.xml:
<action name="download" class="xxx.FileAction">
? <result type="stream">
? ? <param name="bufferSize">2048</param>
? ? <param name="contentDisposition">attachment;filename=${file.name}</param>
? </result>
</action>
action:
// Define
private File file;
private InputStream inputStream;
public String download() {
? ? // Data
? ? file = new File("D:/aaa/abc.jpg");
? ? inputStream = new FileInputstream(file);
? ? return "success";
}
防止重復(fù)提交?
三種情況會(huì)引發(fā)重復(fù)提交:
? ? 1耻矮、多次點(diǎn)擊
? ? 2秦躯、回退,再提交
? ? 3裆装、轉(zhuǎn)發(fā)時(shí) F5 刷新
解決方案:使用 token/tokenSession 攔截器
? ? 1踱承、在配置文件中添加?token/tokenSession?攔截器 (它不包含在 defaultStack 中)
? ? 2、在 form 中添加標(biāo)簽?<s:token />?(會(huì)在頁(yè)面生成一個(gè) hidden 域并將值保存在 session 中)
? ? 3哨免、若使用 token 攔截器: 出錯(cuò)后會(huì)有頁(yè)面跳轉(zhuǎn)勾扭,所以需要配置一個(gè)名為?token.valid?的 result
? ? 4、若使用 tokenSession 攔截器:出錯(cuò)后頁(yè)面不會(huì)發(fā)生變化铁瞒,所以不需要其他配置