由於攔截器的重要性,決定將其從(struts2.1兩天快速入門第一天下午 抽取出來講)
本講將結合模擬用戶權限驗證展開:判斷用戶是否有權限請求訪問某一模塊或頁面.
第九講、自定義攔截器
9.1 首先定義一個User實體類,如下:
public class User implements Serializable {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
9.2 編寫一個用戶登陸的Action類,如下:
public class UserLoginAction {
//接收復合類型的參數,與struts1.x相類似,此時表單的元素名稱
//應該爲:user.username user.password
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String execute(){
//模擬用戶登陸,實際應用是從數據庫裏取的,這裏只是爲了方便測試和學習
if("yulon".equals(user.getUsername())&&"123456".equals(user.getPassword())){
//爲了不讓我們看到真正的session,struts2框架對其作了一層封裝,用一個Map對象來存儲.
//Map map = ActionContext.getContext().getSession();
//map.put("user", user);
//把兩段代碼合成一段,就不用導入Map類
ActionContext.getContext().getSession().put("user", user);
return Action.SUCCESS; //返回到歡迎頁面
}
ActionContext.getContext().put("msg", "用戶登陸失敗!");
//返回到登陸頁面,struts定義了一系列字符串常量,方便用戶使用,統一的好處
return Action.LOGIN;
}
}
知識提示:注意這裏返回的是login,而不是LOGIN.查看ActionContext部分源碼及相關文檔可知,它是一個與線程相關的類,同一個線程內獲取的都是同一個ActionContext實例,原理與ThreadLocal相關,之前有講過ThreadLocal相關知識,ThreadLocal實現在同一個線程內的數據共享,可以更深入得理解ActionContext底層的實現原理.
9.3 在struts.xml配置文件裏新增一個package包,名稱叫testinterceptor,命名空間定義爲/test2,如下配置:
<package name="testinterceptor" namespace="/test2" extends="struts-default">
<!--定義一個轉發到登陸頁面的Action-->
<action name="loginUI">
<result>/WEB-INF/jsp/login.jsp</result>
</action>
<action name="login" class="cn.gkit.action.UserLoginAction" method="execute">
<!--重定向到同一個包內名字叫index的Action-->
<result name="success" type="redirectAction">index</result>
<result name="login">/WEB-INF/jsp/login.jsp</result>
</action>
<!--爲了方便測試,同時也定義一個登出的Action-->
<action name="logout" class="cn.gkit.action.UserLogoutAction" method="execute">
<result name="success" type="redirectAction">loginUI</result>
</action>
<!--用戶登陸成功後的歡迎頁面-->
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
</action>
</package>
9.4 編寫UserLogoutAction類
public class UserLogoutAction {
public String execute(){
//取出當前登陸的用戶
User user = (User)ActionContext.getContext().getSession().get("user");
if(user!=null){
//註銷用戶信息
ActionContext.getContext().getSession().remove("user");
}
return Action.SUCCESS;
}
}
9.5 編寫\WEB-INF\jsp\login.jsp頁面
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>用戶登陸</title>
</head>
<span>${msg}</span>
<body>
<s:form action="login" namespace="/test2" method="post">
用戶名: <s:textfield title="填寫用戶名" name="user.username" ></s:textfield><br/>
密 碼 :<s:password title="填寫密碼" name="user.password"></s:password><br/>
<s:submit value="登陸"></s:submit>
</s:form>
</body>
</html>
知識提示:使用struts2標籤首先在導入相關uri, 可以在struts2核心包下的META-INF目錄下找到struts-tags.tld文件,裏面就有uri的定義,具體標籤的使用暫不在本章細講,大家可以先琢磨一下.
9.6 編寫\WEB-INF\jsp\welcome.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>歡迎首頁</title>
</head>
<body>
歡迎用戶${sessionScope.user.username}的到來!<br />
<a href='<s:url action="logout" namespace="/test2" />'>退出</a>
</body>
</html>
在沒有使用攔截器之前,是可以直接訪問/struts2.1/test2/index請求的.
9.7實現自己的攔截器cn.gkit.web.interceptor.UserAuthInterceptor(有三種方法)
第一種方法是實現com.opensymphony.xwork2.interceptor.Interceptor接口,實現相應的方法就行.
public class UserAuthInterceptor implements Interceptor{
public void destroy() {
}
public void init() {
init() System.out.println("服務器啓動過程中自動加載");
}
public String intercept(ActionInvocation invocation) throws Exception {
//ActionContext.getContext().getSession().get("user");
Map map = invocation.getInvocationContext().getSession();
if(map.get("user")==null){
System.out.println("用戶還沒登陸");
return Action.LOGIN; //struts定義了一系統字符串常量,注意這裏返回的是login,而不是LOGIN.
}else
{
String result = invocation.invoke();//如果用戶已登陸就通過驗證,繼承執行下一個攔截器
System.out.println("返回結果:"+result);
return result;
}
}
}
知識提示:invocation.getInvocationContext()獲取到的是一個與ActionInvocation相關的ActionContext ,但使用ActionContext.getContext()也一樣可以取得.
第二種方法是繼承AbstractInterceptor抽象,此類的設計思想跟之前講的類型轉換器類似.它本身也是實現了Interceptor接口,由於我們一般都不用到init()方法和destroy()方法,因此通常情況下都是通過繼承AbstractInterceptor來實現我們的攔截器.如下:
public class UserAuthInterceptor extends AbstractInterceptor{
@Override
public String intercept(ActionInvocation invocation) throws Exception {
//ActionContext.getContext().getSession().get("user");
Map map = invocation.getInvocationContext().getSession();
if(map.get("user")==null){
System.out.println("用戶還沒登陸");
return Action.LOGIN;
}else
{
String result = invocation.invoke();
System.out.println("返回結果:"+result);
return result;
}
}
}
知識提示:查看AbstractInterceptor源碼,你會感到驚訝,如下:
public abstract class AbstractInterceptor implements Interceptor {
public void init() {
}
public void destroy() {
}
public abstract String intercept(ActionInvocation invocation) throws Exception;
}
知識提示:你沒有看錯,就是幾行代碼,它只是幫我們實現了兩個不常用到的方法,並且還是空實現. 但裏面包含的一種設計思想值得我們去學習研究,所以說學習框架的最高境界是學習它的思想,思想就存在代碼當中,大家有時間可以多查看一下它的源碼.
9.8 將我們編寫好的攔截器類加載到struts.xml配置文件中,如下定義:
<interceptors>
<!-- 加載自己編寫的攔截器 -->
<interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />
</interceptors>
9.9 將我們編寫好的攔截器應用到具體的action類上,將struts.xml 名稱爲index的<action>改如下:
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
<interceptor-ref name="authInterceptor"></interceptor-ref>
</action>
此時:若訪問/struts2.1/test2/index,請求將先會被自己定義的authInterceptor攔截器攔截,執行intercept方法的代碼,判斷用戶若沒有登陸則轉到登陸頁面,若存在纔會放行(即執行下一步,這裏的下一步會執行請求對應的Action方法).
問題:爲什麼說攔截器是struts2的核心呢?是因爲struts2的很多工作都是通過攔截器來實現的,在你定義的<package>的同時,只要你extends 了struts-default包,就同時擁有了struts-default包內定義的全部內容.通過查看struts-default.xml文件可知,它裏面定義了很多攔截器,每個攔截器負責完成不同的工作.如裏面名字爲params的攔截器的作用是會將頁面表單的參數會自動賦值到action裏的屬性。其中在最後定義了一個默認的攔截器棧<default-interceptor-ref name="defaultStack"/>,默認情況下默認攔截器棧會應用到包內定義的所有action身上. 但如果你手工在一個<action>添加一個額外的攔截器後,此時默認的攔截器棧對本<action>不起作用.解決的辦法如下:
第一種解決方案:重新引入defaultStack攔截器棧,將index改如下:
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
<interceptor-ref name="authInterceptor"></interceptor-ref>
<!-- 重新引入 defaultStack-->
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
第二種解決方案:定義自已的攔截器棧,如下:
<interceptors>
<!-- 加載自己編寫的攔截器 -->
<interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />
<!-- 定義攔截器棧 -->
<interceptor-stack name="myInterceptorStack">
<!-- 引用用戶定義的攔截器 -->
<interceptor-ref name="authInterceptor"></interceptor-ref>
<!-- 同時也要重新把默認的攔截器棧引入進來 -->
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
將自己定義好的攔截器棧應用到具體action類中,如將<action>改寫成:
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
<!-- 引入自定義攔截器棧 -->
<interceptor-ref name="myInterceptorStack"></interceptor-ref>
</action>
第三種解決方案:重新定義默認攔截器棧
<interceptors> <!-- 加載自己編寫的攔截器 --> <interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" /> <!-- 定義攔截器棧 --> <interceptor-stack name="myInterceptorStack"> <!-- 引用用戶定義的攔截器 --> <interceptor-ref name="authInterceptor"></interceptor-ref> <!-- 同時也要重新把默認的攔截器棧引入進來 --> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 重新定義默認的攔截器棧,覆蓋掉struts-default定義的默認攔截器棧 --> <default-interceptor-ref name="myInterceptorStack"></default-interceptor-ref>
注意:此時在testinterceptor包內定義的所有<action>默認都會被myInterceptorStack攔截器攔截,index不用重新定義攔截器,改如下:
<action name="index"> <result name="success">/WEB-INF/jsp/welcome.jsp</result> <result name="login" type="redirectAction">loginUI</result> </action>
具體使用哪一種方法按項目需求而定
9.10 在 9.7節裏面還有一種自定義攔截器的方法沒講到:就是方法過濾攔截器(MethodFilterInterceptor)
編寫方法過濾攔截器第一步:編寫繼承於MethodFilterInterceptor抽象類的攔截器
public class MyMethodFilterInterceptor extends MethodFilterInterceptor {
@Override
protected String doIntercept(ActionInvocation invocation) throws Exception {
System.out.println("執行了MyMethodFilterInterceptor攔截器");
String resultString = invocation.invoke();
System.out.println("返回的結果:"+resultString);
return resultString;
}
}
第二步,在上次編寫的test包內加載這個攔截器,如下:<interceptors> <interceptor name="mymethodinterceptor" class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"></interceptor> </interceptors>第三步,在具體的action裏應用該攔截器,如下:
<package name="test" namespace="/test" extends="gkit"> <interceptors> <!-- 定義方法過濾攔截器 --> <interceptor name="mymethodinterceptor" class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"/> </interceptors> <action name="*User" class="cn.gkit.action.HelloWorldAction" method="{1}" > <param name="message">屬性注入</param> <result name="success">/WEB-INF/jsp/helloworld.jsp</result> <interceptor-ref name="mymethodinterceptor"> <!--指定要攔截的方法 includeMethods的優先級比excludeMethods的要高--> <param name="includeMethods">add,execute</param> </interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </action> </package>