本文包括:
1、如何在 Struts2 中使用 Servlet 的相關(guān) API?
2、分析 <result> 結(jié)果頁面
3、Struts2 的數(shù)據(jù)封裝
4、Struts2 攔截器(重難點(diǎn))
5、如何自定義一個(gè) Struts2 攔截器?
1、如何在 Struts2 中使用 Servlet 的相關(guān) API?
-
在 Action 類中也可以獲取到 Servlet 一些常用的API
-
需求:提供 JSP 的表單頁面的數(shù)據(jù),在 Action 中使用 Servlet 的 API 接收到,然后保存到三個(gè)域?qū)ο笾校詈笤亠@示到 JSP 的頁面上。
-
提供 JSP 注冊(cè)的頁面,演示下面這三種方式
<h3>注冊(cè)頁面</h3> <form action="${ pageContext.request.contextPath }/xxx.action" method="post"> 姓名:<input type="text" name="username" /><br/> 密碼:<input type="password" name="password" /><br/> <input type="submit" value="注冊(cè)" /> </form>
-
-
-
完全解耦合的方式(不推薦)
如果使用該種方式,Struts2 框架中提供了一個(gè)類,ActionContext 類,該類中提供一些方法,通過方法獲取 Servlet 的 API
-
一些常用的方法如下
static ActionContext getContext() -- 獲取 ActionContext 對(duì)象實(shí)例
java.util.Map<java.lang.String,java.lang.Object> getParameters() -- 獲取請(qǐng)求參數(shù),相當(dāng)于 request.getParameterMap();
java.util.Map<java.lang.String,java.lang.Object> getSession() -- 獲取的代表 session 域的 Map 集合,就相當(dāng)于操作 session 域。
java.util.Map<java.lang.String,java.lang.Object> getApplication() -- 獲取代表 application 域的Map集合
void put(java.lang.String key, java.lang.Object value) -- 注意:向 request 域中存入值。
-
demo:
public class Demo1Action extends ActionSupport{ private static final long serialVersionUID = -7255855724015241518L; public String execute() throws Exception { // 完全解耦合的方式 ActionContext context = ActionContext.getContext(); // 獲取到請(qǐng)求的參數(shù),封裝所有請(qǐng)求的參數(shù) Map<String, Object> map = context.getParameters(); // 遍歷獲取數(shù)據(jù) Set<String> keys = map.keySet(); for (String key : keys) { // 通過key,來獲取到值 String [] vals = (String[]) map.get(key); System.out.println(key+" : "+Arrays.toString(vals)); } // 如果向request對(duì)象中存入值 context.put("msg", "小東東"); // 獲取其他map集合 context.getSession().put("msg", "小蒼"); context.getApplication().put("msg", "小澤"); return SUCCESS; } }
-
struts.xml 很簡(jiǎn)單,在這里就不給出了,然后在跳轉(zhuǎn)頁面 suc.jsp 中這樣編寫代碼,最后瀏覽器頁面依次顯示:小蒼 小東東 小澤
<body> <h3>使用EL表達(dá)式獲取值</h3> ${ sessionScope.msg } ${ requestScope.msg } ${ applicationScope.msg } </body>
-
使用原生 Servlet 的 API 的方式(推薦)
Struts2 框架提供了一個(gè)類,ServletActionContext,該類中提供了一些靜態(tài)的方法
-
具體的方法如下
getPageContext()
getRequest()
getResponse()
getServletContext()
-
demo:
public class Demo2Action extends ActionSupport{ private static final long serialVersionUID = -864657857993072618L; public String execute() throws Exception { // 獲取到request對(duì)象 HttpServletRequest request = ServletActionContext.getRequest(); request.setAttribute("msg", "小東東"); request.getSession().setAttribute("msg", "美美"); ServletActionContext.getServletContext().setAttribute("msg", "小鳳"); return SUCCESS; } }
跳轉(zhuǎn)頁面和上面的一樣,這次瀏覽器顯示:美美 小東東 小鳳
-
還可以用輸出流打印信息,在 return 前加入
HttpServletResponse response = ServletActionContext.getResponse(); ...
2、分析 <result>
結(jié)果頁面
-
結(jié)果頁面存在兩種形式
-
全局結(jié)果頁面
條件:如果
<package>
包中的一些 action 都返回 success,并且返回的頁面都是同一個(gè) JSP 頁面,這樣就可以配置全局的結(jié)果頁面。全局結(jié)果頁面針對(duì)的當(dāng)前的包中的所有的 Action,但是如果局部還有結(jié)果頁面,會(huì)優(yōu)先跳轉(zhuǎn)到局部的。
-
全局結(jié)果頁面配置代碼如下,與
<action>
標(biāo)簽平行<global-results> <result>/demo3/suc.jsp</result> </global-results>
-
局部結(jié)果頁面
<result>/demo3/suc.jsp</result>
-
demo:
<package name="demo1" extends="struts-default" namespace="/"> <!-- 配置全局的結(jié)果頁面 --> <global-results> <result name="success" type="redirect">/demo1/suc.jsp</result> </global-results> <action name="demo1Action" class="com.itheima.demo1.Demo1Action"> <result name="success">/demo1/suc.jsp</result> </action> </package>
-
-
結(jié)果頁面的類型
-
結(jié)果頁面使用
<result>
標(biāo)簽進(jìn)行配置,包含兩個(gè)屬性name -- 邏輯視圖的名稱
-
type -- 跳轉(zhuǎn)的類型,需要掌握一些常用的類型。常見的結(jié)果類型在 struts-default.xml 中查找。
dispatcher -- 轉(zhuǎn)發(fā),type 的默認(rèn)值,Action--->JSP
redirect -- 重定向, Action--->JSP
chain -- 多個(gè) action 之間跳轉(zhuǎn).從一個(gè) Action 轉(zhuǎn)發(fā)到另一個(gè)Action. Action---Action
-
redirectAction -- 多個(gè) action 之間跳轉(zhuǎn).從一個(gè) Action 重定向到另一個(gè) Action. Action---Action
<!-- 演示重定向到 Action --> <action name="demo3Action_*" class="com.itheima.demo1.Demo3Action" method="{1}"> <result name="success" type="redirectAction">demo3Action_update</result> </action>
上面的配置代碼演示了如何編寫 redirectAction 類型的結(jié)果頁面,效果是:當(dāng)訪問 demo3Action 的任何方法時(shí),若成功,則會(huì)再執(zhí)行 update 方法,這個(gè)很常用。
stream -- 文件下載時(shí)候使用的
-
3、Struts2 的數(shù)據(jù)封裝
-
為什么要使用數(shù)據(jù)的封裝呢?
作為 MVC 框架,必須要負(fù)責(zé)解析 HTTP 請(qǐng)求參數(shù),并將其封裝到 Model 對(duì)象中
封裝數(shù)據(jù)為開發(fā)提供了很多方便
Struts2 框架提供了很強(qiáng)大的數(shù)據(jù)封裝的功能,不再需要使用 Servlet 的 API 完成手動(dòng)封裝了!!
-
Struts2 中提供了兩類數(shù)據(jù)封裝的方式
-
第一種方式:屬性驅(qū)動(dòng)(不推薦)
-
Action 類提供對(duì)應(yīng)屬性的 set 方法進(jìn)行數(shù)據(jù)的封裝。
表單的哪些屬性需要封裝數(shù)據(jù),那么在對(duì)應(yīng)的 Action 類中提供該屬性的 set 方法即可。
表單中的數(shù)據(jù)提交,最終找到 Action 類中的 setXxx 的方法,最后賦值給全局變量。
-
demo:
public class Regist1Action extends ActionSupport{ private static final long serialVersionUID = -966487869258031548L; private String username; private String password; private Integer age; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAge(Integer age) { this.age = age; } public String execute() throws Exception { System.out.println(username+" "+password+" "+age); return NONE; } }
注意:
Struts2 采用的攔截器完成數(shù)據(jù)的封裝。
這種方式不是特別好:因?yàn)閷傩蕴貏e多,提供特別多的 set 方法,而且還需要手動(dòng)將數(shù)據(jù)存入到對(duì)象中。
這種情況下,Action 類就相當(dāng)于一個(gè) JavaBean,就沒有體現(xiàn)出 MVC 的思想,Action 類又封裝數(shù)據(jù),又接收請(qǐng)求處理,耦合性較高。
-
上面的代碼不太合理,應(yīng)該把那些屬性封裝到 JavaBean 中,所以首先創(chuàng)建 JavaBean ,如下:
public class User { private String username; private String password; private Integer age; ...//省略 get 和 set 方法 }
我們?cè)賱?chuàng)建 Regist2Action.java,代碼如下:
public class Regist2Action extends ActionSupport{ private static final long serialVersionUID = 6556880331550390473L; // 注意二:屬性驅(qū)動(dòng)的方式,現(xiàn)在,要提供是get和set方法 private User user; public User getUser() { System.out.println("getUser..."); return user; } public void setUser(User user) { System.out.println("setUser..."); this.user = user; } public String execute() throws Exception { System.out.println(user); return NONE; } }
-
在 jsp 頁面上,使用 OGNL 表達(dá)式進(jìn)行數(shù)據(jù)封裝。
在頁面中使用 OGNL 表達(dá)式進(jìn)行數(shù)據(jù)的封裝,就可以直接把屬性封裝到某一個(gè) JavaBean 的對(duì)象中。
-
頁面中的編寫發(fā)生了變化,需要使用 OGNL 的方式,jsp 如下:
<h3>屬性驅(qū)動(dòng)的方式(把數(shù)據(jù)封裝到JavaBean的對(duì)象中)</h3> <!-- 注意一:頁面的編寫規(guī)則,發(fā)生了變化,使用的OGNL表達(dá)式的寫法 --> <form action="${ pageContext.request.contextPath }/regist2.action" method="post"> 姓名:<input type="text" name="user.username" /><br/> 密碼:<input type="password" name="user.password" /><br/> 年齡:<input type="password" name="user.age" /><br/> <input type="submit" value="注冊(cè)" /> </form>
-
注意:只提供一個(gè) set 方法還不夠,必須還需要提供 user 屬性的 get 和 set 方法!!!
原理過程:先調(diào)用 get 方法,判斷一下是否有 user 對(duì)象的實(shí)例對(duì)象,如果沒有,調(diào)用 set 方法把攔截器創(chuàng)建的對(duì)象注入進(jìn)來,
-
-
第二種方式:模型驅(qū)動(dòng)(推薦)
使用模型驅(qū)動(dòng)的方式,也可以把表單中的數(shù)據(jù)直接封裝到一個(gè) JavaBean 的對(duì)象中,并且 jsp 頁面中表單的寫法和之前的寫法沒有區(qū)別!
-
模型驅(qū)動(dòng)的編寫步驟:
-
手動(dòng)實(shí)例化 JavaBean,即:
private User user = new User();
必須實(shí)現(xiàn)
ModelDriven<T>
接口,實(shí)現(xiàn)getModel()
的方法,在getModel()
方法中返回 user 即可!!
-
demo:
/** * 模型驅(qū)動(dòng)的方式 * 實(shí)現(xiàn)ModelDriven接口 * 必須要手動(dòng)實(shí)例化對(duì)象(需要自己new好) * @author Administrator */ public class Regist3Action extends ActionSupport implements ModelDriven<User>{ private static final long serialVersionUID = 6556880331550390473L; // 必須要手動(dòng)實(shí)例化 private User user = new User(); // 獲取模型對(duì)象 public User getModel() { return user; } public String execute() throws Exception { System.out.println(user); return NONE; } }
-
-
-
數(shù)據(jù)封裝到集合中
封裝復(fù)雜類型的參數(shù)(集合類型 Collection 、Map接口等)
需求:頁面中有可能想批量添加一些數(shù)據(jù),那么現(xiàn)在就可以使用這種方法,把數(shù)據(jù)封裝到集合中。
-
把數(shù)據(jù)封裝到 Collection 中
-
因?yàn)?Collection 接口都會(huì)有下標(biāo)值,所有頁面的寫法會(huì)有一些區(qū)別,注意:
<input type="text" name="products[0].name" />
在 Action 中的寫法,需要提供 products 的集合,并且提供 get 和 set 方法。
-
以 list 為例:
jsp:
<h3>向List集合封裝數(shù)據(jù)(默認(rèn)情況下,采用是屬性驅(qū)動(dòng)的方式)</h3> <!-- 后臺(tái):List<User> list --> <form action="${ pageContext.request.contextPath }/regist4.action" method="post"> 姓名:<input type="text" name="list[0].username" /><br/> 密碼:<input type="password" name="list[0].password" /><br/> 年齡:<input type="password" name="list[0].age" /><br/> 姓名:<input type="text" name="list[1].username" /><br/> 密碼:<input type="password" name="list[1].password" /><br/> 年齡:<input type="password" name="list[1].age" /><br/> <input type="submit" value="注冊(cè)" /> </form>
Action:
/** * 屬性驅(qū)動(dòng)的方式,把數(shù)據(jù)封裝到List集合中 * @author Administrator */ public class Regist4Action extends ActionSupport{ private static final long serialVersionUID = 6556880331550390473L; private List<User> list; public List<User> getList() { return list; } public void setList(List<User> list) { this.list = list; } public String execute() throws Exception { for (User user : list) { System.out.println(user); } return NONE; } }
-
-
把數(shù)據(jù)封裝到 Map 中
-
Map 集合是鍵值對(duì)的形式,頁面的寫法
<input type="text" name="map['one'].name" />
Action 中提供 map 集合,并且提供 get 和 set 方法
-
jsp:
<h3>向Map集合封裝數(shù)據(jù)(默認(rèn)情況下,采用是屬性驅(qū)動(dòng)的方式)</h3> <form action="${ pageContext.request.contextPath }/regist5.action" method="post"> 姓名:<input type="text" name="map['one'].username" /><br/> 密碼:<input type="password" name="map['one'].password" /><br/> 年齡:<input type="password" name="map['one'].age" /><br/> 姓名:<input type="text" name="map['two'].username" /><br/> 密碼:<input type="password" name="map['two'].password" /><br/> 年齡:<input type="password" name="map['two'].age" /><br/> <input type="submit" value="注冊(cè)" /> </form>
-
Action:
/** * 屬性驅(qū)動(dòng)的方式,把數(shù)據(jù)封裝到map集合中 * @author Administrator */ public class Regist5Action extends ActionSupport{ private static final long serialVersionUID = 6556880331550390473L; private Map<String, User> map; public Map<String, User> getMap() { return map; } public void setMap(Map<String, User> map) { this.map = map; } public String execute() throws Exception { System.out.println(map); return NONE; } }
-
4、Struts2 攔截器(重難點(diǎn))
-
攔截器的概述
攔截器就是 AOP(Aspect-Oriented Programming)的一種實(shí)現(xiàn)。(AOP 是指用于在某個(gè)方法或字段被訪問之前,進(jìn)行攔截然后在之前或之后加入某些操作。)
過濾器:過濾從客服端發(fā)送到服務(wù)器端請(qǐng)求的
攔截器:攔截器不能攔截 JSP,只能攔截對(duì)目標(biāo) Action 中的某些方法進(jìn)行攔截(進(jìn)出 Action 時(shí)都進(jìn)行攔截)
-
攔截器和過濾器的區(qū)別
攔截器是基于 JAVA 反射機(jī)制的,而過濾器是基于函數(shù)回調(diào)的
過濾器依賴于Servlet容器,而攔截器不依賴于 Servlet 容器
-
攔截器只能對(duì) Action 請(qǐng)求起作用(Action 中的方法),而過濾器可以對(duì)幾乎所有的請(qǐng)求起作用(CSS JSP JS)
-
攔截器 采用 責(zé)任鏈 模式,類似過濾器的過濾鏈
在責(zé)任鏈模式里,很多對(duì)象由每一個(gè)對(duì)象對(duì)其下家的引用而連接起來形成一條鏈
責(zé)任鏈每一個(gè)節(jié)點(diǎn),都可以繼續(xù)調(diào)用下一個(gè)節(jié)點(diǎn),也可以阻止流程繼續(xù)執(zhí)行
在struts2 中可以定義很多個(gè)攔截器,將多個(gè)攔截器按照特定順序 組成攔截器棧 (順序調(diào)用棧中的每一個(gè)攔截器 )
-
-
Struts2 的核心是攔截器,看一下 Struts2 的運(yùn)行流程
請(qǐng)求提交到服務(wù)器端,由 ActionMapper 解析,然后會(huì)先經(jīng)過 Struts 2 的核心過濾器(StrutsPrepareAndExecuteFilter),通過源碼可以發(fā)現(xiàn),在這時(shí)會(huì)得到 namespace 、name、method,再根據(jù) Configuration Manager 和 Struts.xml,它們是關(guān)于配置的信息,接著創(chuàng)建 ActionProxy,再由 ActionProxy 創(chuàng)建 ActionInvocation ,它負(fù)責(zé)調(diào)用所有的 Action,然后經(jīng)過層層 Interceptor(攔截器)到達(dá)視圖模版(JSP、FreeMarker、Velocity 等等),離開視圖模版后又進(jìn)入層層攔截器,最后作出響應(yīng),返回給客戶端。
5、如何自定義一個(gè) Struts2 攔截器?
-
編寫攔截器,需要實(shí)現(xiàn) Interceptor 接口,實(shí)現(xiàn)接口中的三個(gè)方法,或者也可以繼承 Interceptor 接口的幾個(gè)實(shí)現(xiàn)類,如下就繼承了 AbstractInterceptor 類,Struts2 已經(jīng)規(guī)定了該類攔截所有 Action 的所有方法:
/** * 編寫簡(jiǎn)單的攔截器 * @author Administrator */ public class DemoInterceptor extends AbstractInterceptor{ private static final long serialVersionUID = 4360482836123790624L; /** * intercept用來進(jìn)行攔截的 */ public String intercept(ActionInvocation invocation) throws Exception { System.out.println("Action方法執(zhí)行之前..."); // 執(zhí)行下一個(gè)攔截器 String result = invocation.invoke(); System.out.println("Action方法執(zhí)行之后..."); return result; } }
-
需要在struts.xml中進(jìn)行攔截器的配置,配置一共有兩種方式
-
第一種,定義攔截器:
<!-- 第一種方式:定義攔截器 --> <interceptors> <interceptor name="DemoInterceptor" class="com.itheima.interceptor.DemoInterceptor"/> </interceptors> <action name="userAction" class="com.itheima.demo3.UserAction"> <!-- 若是簡(jiǎn)單的引用自己的攔截器,那么默認(rèn)棧(defaultStack)的攔截器就不執(zhí)行了,必須要手動(dòng)引入默認(rèn)棧 --> <interceptor-ref name="DemoInterceptor"/> <interceptor-ref name="defaultStack"/> </action>
-
第二種,定義攔截器棧:
<!-- 第二種方式:定義攔截器棧 --> <interceptors> <interceptor name="DemoInterceptor" class="com.itheima.interceptor.DemoInterceptor"/> <!-- 定義攔截器棧 --> <interceptor-stack name="myStack"> <interceptor-ref name="DemoInterceptor"/> <interceptor-ref name="defaultStack"/> </interceptor-stack> </interceptors> <action name="userAction" class="com.itheima.demo3.UserAction"> <!-- 引入攔截器棧就OK --> <interceptor-ref name="myStack"/> </action>
-
-
案例:使用攔截器判斷用戶是否已經(jīng)登錄
-
首先自定義攔截器類:UserInterceptor,注意:在這里不能繼承 AbstractInterceptor 類,因?yàn)樵擃悢r截所有方法,若把登陸方法也攔截了,那永遠(yuǎn)也登陸不了了,在這里我們可以選擇 MethodFilterInterceptor 類,它可以配置哪些攔截,哪些不攔截
/** * 自定義攔截器,判斷當(dāng)前系統(tǒng)是否已經(jīng)登錄,如果登錄,繼續(xù)執(zhí)行。如果沒有登錄,跳轉(zhuǎn)到登錄頁面 * @author Administrator */ public class UserInterceptor extends MethodFilterInterceptor{ private static final long serialVersionUID = 335018670739692955L; /** * 進(jìn)行攔截的方法 */ protected String doIntercept(ActionInvocation invocation) throws Exception { // 獲取session對(duì)象 User user = (User) ServletActionContext.getRequest().getSession().getAttribute("existUser"); if(user == null){ // 沒有登錄,直接返回一個(gè)字符串,后面就不會(huì)執(zhí)行了 return "login"; } return invocation.invoke(); } }
-
然后配置 struts.xml,定義全局結(jié)果頁面 login,然后在用戶模塊的登陸功能中,使攔截失效,注意失效是如何配置的
<?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="crm" namespace="/" extends="struts-default"> <!-- 配置攔截器 --> <interceptors> <interceptor name="UserInterceptor" class="com.itheima.interceptor.UserInterceptor"/> </interceptors> <global-results> <result name="login">/login.htm</result> </global-results> <!-- 配置用戶的模塊 --> <action name="user_*" class="com.itheima.action.UserAction" method="{1}"> <!-- <result name="login">/login.htm</result> --> <result name="success">/index.htm</result> <interceptor-ref name="UserInterceptor"> <!-- login方法不攔截 --> <param name="excludeMethods">login</param> </interceptor-ref> <interceptor-ref name="defaultStack"/> </action> <!-- 客戶模塊 --> <action name="customer_*" class="com.itheima.action.CustomerAction" method="{1}"> <interceptor-ref name="UserInterceptor"/> <interceptor-ref name="defaultStack"/> </action> </package> </struts>
之前在 Java web 階段學(xué)習(xí)了過濾器(Filter),它也可以用來判斷用戶是否已經(jīng)登陸,但是注意兩者的區(qū)別,過濾器可以過濾所有的 URL,攔截器只能在訪問與離開 Action 的時(shí)候進(jìn)行攔截。
-