Struts2從上到下的詳解


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ā)生變化铁瞒,所以不需要其他配置

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妙色,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慧耍,更是在濱河造成了極大的恐慌身辨,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芍碧,死亡現(xiàn)場(chǎng)離奇詭異煌珊,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)泌豆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)定庵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事蔬浙≈砺洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵畴博,是天一觀的道長(zhǎng)笨忌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)俱病,這世上最難降的妖魔是什么官疲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮亮隙,結(jié)果婚禮上途凫,老公的妹妹穿的比我還像新娘。我一直安慰自己溢吻,他們只是感情好颖榜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著煤裙,像睡著了一般掩完。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上硼砰,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天且蓬,我揣著相機(jī)與錄音,去河邊找鬼题翰。 笑死恶阴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豹障。 我是一名探鬼主播冯事,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼血公!你這毒婦竟也來(lái)了昵仅?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤累魔,失蹤者是張志新(化名)和其女友劉穎摔笤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體垦写,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吕世,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梯投。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片命辖。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡况毅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尔艇,到底是詐尸還是另有隱情尔许,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布漓帚,位于F島的核電站,受9級(jí)特大地震影響午磁,放射性物質(zhì)發(fā)生泄漏尝抖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一迅皇、第九天 我趴在偏房一處隱蔽的房頂上張望昧辽。 院中可真熱鬧,春花似錦登颓、人聲如沸搅荞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)咕痛。三九已至,卻和暖如春喇嘱,著一層夾襖步出監(jiān)牢的瞬間茉贡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工者铜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腔丧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓作烟,卻偏偏與公主長(zhǎng)得像愉粤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拿撩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容