Struts2攔截器之ExceptionMappingInterceptor(異常映射攔截器)


一、異常攔截器是什么?

異常攔截器的作用是提供一個機會,可以設置在action執行過程中發生異常的時候映射到一個結果字符串而不是直接中斷

將異常整合到業務邏輯中,比如在分層系統的調用中可以從底層拋出一個異常,高層捕捉到這個異常就知道發生了什么事情啦。

二、如何使用?

1.兩種異常映射類型:

1.1.global

global的異常映射對整個package下的action都有效:

<struts>

    <package name="default" namespace="/" extends="struts-default">
    
        <!-- 全局異常配置 -->
        <global-exception-mappings>
            <exception-mapping result="login" exception="struts_practice_004.UsernameIsNullException"  />    
        </global-exception-mappings>
        
        <action name="loginAction" class="struts_practice_004.LoginAction">
            
            <result name="login">/login.jsp</result>
            <result name="success">/success.jsp</result>
        </action>
        
    </package>
</struts>

1.2.action

action級別的異常映射只對單個的action有效:

<struts>

    <package name="default" namespace="/" extends="struts-default">
    
        <action name="loginAction" class="struts_practice_004.LoginAction">
        
            <!-- action級別的異常映射 -->
            <exception-mapping exception="struts_practice_004.UsernameIsNullException" result="login" />
            
            <result name="login">/login.jsp</result>
            <result name="success">/success.jsp</result>
        </action>
        
    </package>
</struts>

2.配置異常映射的可選參數

logEnabled 日志是否開啟,默認關閉

logCategory 日志種類

logLevel 日志級別

3.使用異常映射的例子:

User實體:

/**
 * 用戶實體
 * @author CC11001100
 *
 */
public class User {

    private String username;
    private String passwd;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

}

LoginAction:

/**
 * 登錄Action
 * 
 * @author CC11001100
 * 
 */
public class LoginAction extends ActionSupport {

    private User user;
    private LoginService loginService;
    
    public LoginAction() {
        loginService=new LoginService();
    }

    @Override
    public String execute() throws Exception {
        
        //嘗試登錄
        User u1=loginService.login(user);
        //如果為空,說明登錄失敗.
        if(u1==null){
            return LOGIN;
        }
        
        return SUCCESS;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

}

LoginService:

public class LoginService {

    /**
     * 登錄是否成功
     * @param user
     * @return
     */
    public User login(User user){
        if(true) throw new UsernameIsNullException();
        return user;
    }
    
}

struts.xml

<struts>

    <package name="default" namespace="/" extends="struts-default">
    
        <action name="loginAction" class="struts_practice_004.LoginAction">
            <exception-mapping exception="struts_practice_004.UsernameIsNullException" result="login" />
            <result name="login">/login.jsp</result>
            <result name="success">/success.jsp</result>
        </action>
        
    </package>
</struts>

前端頁面:

<form    action="loginAction" method="post">
     用戶名:<input type="text" name="user.username" /><br/>&nbsp;&nbsp;碼:<input type="password" name="user.passwd" /><br/>
     <input type="submit" value="登錄" /><s:property value="exception"/>
 </form>
  
  <s:debug />

結果:

提示不太友好,感覺例子設計得很牽強....湊活着吧,我們都知道Action會被壓入值棧的棧頂,但是這里有一個<s:property value="exception"/> 這個是怎么訪問得到的呢?請看下面的源代碼分析。

三、工作原理?

異常攔截器處在默認攔截器棧的第一層,它的實現類是ExceptionMappingInterceptor,源代碼如下:

  1 /**
  2  * 
  3  *  異常映射攔截器:
  4  *  
  5  *  1.捕捉異常
  6  *  2.將異常映射到結果字符串
  7  *
  8  */
  9 public class ExceptionMappingInterceptor extends AbstractInterceptor {
 10     
 11     protected static final Logger LOG = LoggerFactory.getLogger(ExceptionMappingInterceptor.class);
 12 
 13     protected Logger categoryLogger;
 14     //是否將異常信息打印到日志
 15     protected boolean logEnabled = false;
 16     //logger的種類
 17     protected String logCategory;
 18     //日志的級別,默認是debug,從哪兒知道的?耐心往下看...
 19     protected String logLevel;
 20     
 21     /*------------------------- 在這下面是getter和setter ------------------------------*/
 22     public boolean isLogEnabled() {
 23         return logEnabled;
 24     }
 25 
 26     public void setLogEnabled(boolean logEnabled) {
 27         this.logEnabled = logEnabled;
 28     }
 29 
 30     public String getLogCategory() {
 31         return logCategory;
 32     }
 33 
 34     public void setLogCategory(String logCatgory) {
 35         this.logCategory = logCatgory;
 36     }
 37 
 38     public String getLogLevel() {
 39         return logLevel;
 40     }
 41 
 42     public void setLogLevel(String logLevel) {
 43         this.logLevel = logLevel;
 44     }
 45     /*----------------------------- getter和setter結束 ----------------------------------*/
 46     
 47     /**
 48      *  攔截器方法
 49      */
 50     @Override
 51     public String intercept(ActionInvocation invocation) throws Exception {
 52         
 53         String result;
 54 
 55         try {
 56             //正常執行,繼續往下遞歸調用,如果執行過程中沒有拋出異常就相當於沒有異常攔截器
 57             //意思就是異常攔截器只有在拋出異常的時候才發揮作用,如果沒發生異常就感覺不到它的存在
 58             result = invocation.invoke();
 59         } catch (Exception e) {
 60             //如果在執行的過程catch到了異常,異常攔截器才會發揮作用
 61             
 62             //如果開啟日志記錄的話,默認是false,上面已經寫死了
 63             if (isLogEnabled()) {
 64                 //記錄一下
 65                 handleLogging(e);
 66             }
 67             
 68             //獲得異常映射信息,這個信息是在struts.xml中的<exception-mapping>中配置的
 69             List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
 70             
 71             //找一下當前拋出的異常有沒有配置對應的結果字符串
 72             String mappedResult = this.findResultFromExceptions(exceptionMappings, e);
 73             
 74             if (mappedResult != null) {
 75                 //如果找到了異常對應的結果字符串,那么保存結果
 76                 result = mappedResult;
 77                 //同時將這個異常包裹起來放到ValueStack的棧頂
 78                 publishException(invocation, new ExceptionHolder(e));
 79             } else {
 80                 //沒有沒有配置結果字符串,我也不知道該怎么處理了,直接往上拋吧
 81                 throw e;
 82             }
 83         }
 84         
 85         //執行正常或者發生異常但配置了異常映射結果字符串才會執行到這里
 86         return result;
 87     }
 88 
 89     /**
 90      * Handles the logging of the exception.
 91      *  打印日志
 92      * @param e  the exception to log.
 93      */
 94     protected void handleLogging(Exception e) {
 95         //決定日志的打印方式
 96         if (logCategory != null) {
 97             //使用categoryLogger
 98             if (categoryLogger == null) {
 99                 // init category logger
100                 categoryLogger = LoggerFactory.getLogger(logCategory);
101             }
102             doLog(categoryLogger, e);
103         } else {
104             //或是使用默認的這個
105             doLog(LOG, e);
106         }
107     }
108     
109     /**
110      * Performs the actual logging.
111      * 
112      * 打印日志的具體實現
113      * 
114      * @param logger  the provided logger to use.
115      * @param e  the exception to log.
116      */
117     protected void doLog(Logger logger, Exception e) {
118         //如果沒有指定logLevel的話,默認就是debug級別了
119         if (logLevel == null) {
120             logger.debug(e.getMessage(), e);
121             return;
122         }
123         
124         //根據指定的日志級別打印
125         if ("trace".equalsIgnoreCase(logLevel)) {
126             logger.trace(e.getMessage(), e);
127         } else if ("debug".equalsIgnoreCase(logLevel)) {
128             logger.debug(e.getMessage(), e);
129         } else if ("info".equalsIgnoreCase(logLevel)) {
130             logger.info(e.getMessage(), e);
131         } else if ("warn".equalsIgnoreCase(logLevel)) {
132             logger.warn(e.getMessage(), e);
133         } else if ("error".equalsIgnoreCase(logLevel)) {
134             logger.error(e.getMessage(), e);
135         } else if ("fatal".equalsIgnoreCase(logLevel)) {
136             logger.fatal(e.getMessage(), e);
137         } else {
138             //都不匹配啊,說明指定的日志級別不合法(這種情況八成是拼寫錯誤,血淚經驗....),拋出異常警告一下搞開發的這傻小子~
139             throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
140         }
141     }
142     
143     /**
144      * 看看映射列表中有沒有對應的映射
145      */
146     protected String findResultFromExceptions(List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
147         String result = null;
148 
149         // Check for specific exception mappings
150         //看看是否配置了異常映射,即<exception-mapping exception="" result="" />
151         if (exceptionMappings != null) {
152             //因為后面要比慘..啊是比小,所以就預先給一個最大值,比較簡單的技巧
153             int deepest = Integer.MAX_VALUE;
154             //遍歷映射列表項
155             for (Object exceptionMapping : exceptionMappings) {
156                 //看不大懂,為什么放着泛型不用呢,直接取出來不就是ExceptionMappingConfig類型的么? - -
157                 ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
158                 //看看當前的異常類跟配置的<exception-mapping exception="就是這一個" />能不能匹配的上,是不是它本身或者其子類
159                 int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
160                 if (depth >= 0 && depth < deepest) {
161                     //說明找到了,當前拋出的異常類是配置的類本身或者是其子類
162                     //按照一般想法找到了就直接return就可以了唄,我剛開始也是這樣子想的,
163                     //但是呢仔細看一看,結合上面的depth < deepest和下面的deepest = depth;
164                     //所以呢,這里只是保存了一下仍然繼續的意思就是要從這些映射列表中找到一個跟當前類最接近的
165                     //這里的接近是從t(此時的t是超類引用子類對象,它的運行時類型並不是Throwable)一直到Throwable
166                     //的這段距離,從[t,Throwable]這個區間上找一個最靠左的,感覺這樣就能解釋清楚了,
167                     //這樣做是為了最細程度的映射
168                     deepest = depth;
169                     result = exceptionMappingConfig.getResult();
170                 }
171             }
172         }
173 
174         return result;
175     }
176 
177     /**
178      * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
179      * Otherwise, returns depth. Lowest depth wins.
180      * 
181      *  找一下這個映射類到拋出的異常類之間有幾個超類,可能有三種情況:
182      *  1. 沒找到:-1表示exceptionMapping不是t的超類(這樣貌似也沒辦法映射到了,映射的話包括本類和子類)
183      *  2. 本身:如果0的話表示exceptionMapping和t就是同一個類
184      *  3. 子類:其它字符串表示中間的超類個數,表示t是exceptionMapping的一個子類,但我們要的是中間的繼承次數
185      * 
186      * @param exceptionMapping  the mapping classname
187      * @param t  the cause
188      * @return the depth, if not found -1 is returned.
189      */
190     public int getDepth(String exceptionMapping, Throwable t) {
191         return getDepth(exceptionMapping, t.getClass(), 0);
192     }
193 
194     /**
195      * 找exceptionMapping到exceptionClass的“距離”
196      */
197     private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
198         //看看這個映射類是不是當前異常類的超類
199         if (exceptionClass.getName().contains(exceptionMapping)) {
200             // Found it!
201             //找到了
202             return depth;
203         }
204         // If we've gone as far as we can go and haven't found it...
205         //已經找到了頂層(Throwable),但仍然未找到,說明不可能找到了,返回不匹配
206         if (exceptionClass.equals(Throwable.class)) {
207             return -1;
208         }
209         
210         //遞歸順着繼承鏈往上找
211         return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
212     }
213 
214     /**
215      * Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
216      * Subclasses may override this to customize publishing.
217      * 
218      *  將這個ExceptionHolder壓入ValueStack的棧頂
219      * 
220      * @param invocation The invocation to publish Exception for.
221      * @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
222      */
223     protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
224         //壓入值棧的棧頂,以便OGNL表達式訪問
225         invocation.getStack().push(exceptionHolder);
226     }
227 }

 放入ValueStack棧頂的wrapper類的源代碼分析:

 1 /**
 2  *
 3  * A simple wrapper around an exception, providing an easy way to print out the stack trace of the exception as well as
 4  * a way to get a handle on the exception itself.
 5  * 
 6  * 將異常wrapper一下,然后提供兩個方法以供簡便的獲得這個異常和異常追蹤棧。
 7  * 
 8  * getException() 
 9  * getExceptionStack() 
10  * 
11  * 這兩個getter方法是用來給OGNL訪問exception信息使用的,
12  * 當發生異常的時候會將異常對象用這個類包裹起來放到ValueStack的棧頂,
13  * 所以直接通過exception就可以訪問得到異常對象本身,
14  * 通過exceptionStack就可以訪問得到這個異常棧信息。
15  * 
16  */
17 public class ExceptionHolder implements Serializable {
18 
19     //要包裹的異常本身
20     private Exception exception;
21 
22     /**
23      * Holds the given exception
24      * 實例化這個類必須傳進來一個異常,它持有一個異常。
25      * @param exception  the exception to hold.
26      */
27     public ExceptionHolder(Exception exception) {
28         this.exception = exception;
29     }
30 
31     /**
32      * Gets the holded exception
33      * 返回持有的異常對象
34      * @return  the holded exception
35      */
36     public Exception getException() {
37         return this.exception;
38     }
39 
40     /**
41      * Gets the holded exception stacktrace using {@link Exception#printStackTrace()}.
42      * 以String的形式返回異常棧追蹤信息
43      * @return  stacktrace
44      */
45     public String getExceptionStack() {
46         String exceptionStack = null;
47         
48         if (getException() != null) {
49             StringWriter sw = new StringWriter();
50             PrintWriter pw = new PrintWriter(sw);
51 
52             try {
53                 getException().printStackTrace(pw);
54                 exceptionStack = sw.toString();
55             }
56             finally {
57                 try {
58                     sw.close();
59                     pw.close();
60                 } catch (IOException e) {
61                     // ignore
62                 }
63             }
64         }
65 
66         return exceptionStack;
67     }
68     
69 }

 用來表示異常映射的類的源代碼:

這個類使用了Builder模式:設計模式之構建者模式(Builder)

  1 /**
  2  * Configuration for exception mapping.
  3  * 異常映射的配置信息
  4  */
  5 public class ExceptionMappingConfig extends Located implements Serializable {
  6     
  7     //結果字符串
  8     private String name;
  9     //異常類的類名
 10     private String exceptionClassName;
 11     //映射到的結果字符串
 12     private String result;
 13     //傳遞的參數
 14     private Map<String,String> params;
 15 
 16     protected ExceptionMappingConfig(String name, String exceptionClassName, String result) {
 17         this.name = name;
 18         this.exceptionClassName = exceptionClassName;
 19         this.result = result;
 20         this.params = new LinkedHashMap<String,String>();
 21     }
 22 
 23     protected ExceptionMappingConfig(ExceptionMappingConfig target) {
 24         this.name = target.name;
 25         this.exceptionClassName = target.exceptionClassName;
 26         this.result = target.result;
 27         this.params = new LinkedHashMap<String,String>(target.params);
 28     }
 29 
 30     public String getName() {
 31         return name;
 32     }
 33 
 34     public String getExceptionClassName() {
 35         return exceptionClassName;
 36     }
 37 
 38     public String getResult() {
 39         return result;
 40     }
 41 
 42     public Map<String,String> getParams() {
 43         return params;
 44     }
 45 
 46 
 47     @Override
 48     public boolean equals(Object o) {
 49         if (this == o) {
 50             return true;
 51         }
 52 
 53         if (!(o instanceof ExceptionMappingConfig)) {
 54             return false;
 55         }
 56 
 57         final ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) o;
 58 
 59         if ((name != null) ? (!name.equals(exceptionMappingConfig.name)) : (exceptionMappingConfig.name != null)) {
 60             return false;
 61         }
 62 
 63         if ((exceptionClassName != null) ? (!exceptionClassName.equals(exceptionMappingConfig.exceptionClassName)) : (exceptionMappingConfig.exceptionClassName != null))
 64         {
 65             return false;
 66         }
 67 
 68         if ((result != null) ? (!result.equals(exceptionMappingConfig.result)) : (exceptionMappingConfig.result != null))
 69         {
 70             return false;
 71         }
 72 
 73         if ((params != null) ? (!params.equals(exceptionMappingConfig.params)) : (exceptionMappingConfig.params != null))
 74         {
 75             return false;
 76         }
 77 
 78         return true;
 79     }
 80 
 81     @Override
 82     public int hashCode() {
 83         int hashCode;
 84         hashCode = ((name != null) ? name.hashCode() : 0);
 85         hashCode = (29 * hashCode) + ((exceptionClassName != null) ? exceptionClassName.hashCode() : 0);
 86         hashCode = (29 * hashCode) + ((result != null) ? result.hashCode() : 0);
 87         hashCode = (29 * hashCode) + ((params != null) ? params.hashCode() : 0);
 88 
 89         return hashCode;
 90     }
 91 
 92     /**
 93      * The builder for this object.  An instance of this object is the only way to construct a new instance.  The
 94      * purpose is to enforce the immutability of the object.  The methods are structured in a way to support chaining.
 95      * After setting any values you need, call the {@link #build()} method to create the object.
 96      */
 97     public static class Builder{
 98 
 99         private ExceptionMappingConfig target;
100 
101         public Builder(ExceptionMappingConfig toClone) {
102             target = new ExceptionMappingConfig(toClone);
103         }
104 
105         public Builder(String name, String exceptionClassName, String result) {
106             target = new ExceptionMappingConfig(name, exceptionClassName, result);
107         }
108 
109         public Builder name(String name) {
110             target.name = name;
111             return this;
112         }
113 
114         public Builder exceptionClassName(String name) {
115             target.exceptionClassName = name;
116             return this;
117         }
118 
119         public Builder result(String result) {
120             target.result = result;
121             return this;
122         }
123 
124         public Builder addParam(String name, String value) {
125             target.params.put(name, value);
126             return this;
127         }
128 
129         public Builder addParams(Map<String,String> params) {
130             target.params.putAll(params);
131             return this;
132         }
133 
134         public Builder location(Location loc) {
135             target.location = loc;
136             return this;
137         }
138 
139         public ExceptionMappingConfig build() {
140             target.params = Collections.unmodifiableMap(target.params);
141             ExceptionMappingConfig result = target;
142             target = new ExceptionMappingConfig(target);
143             return result;
144         }
145     }
146 
147 }

 這是它的內存里的表示:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM