一、異常攔截器是什么?
異常攔截器的作用是提供一個機會,可以設置在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/> 密 碼:<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 }
這是它的內存里的表示: