JAVA EE項目中,不論是對底層的數據操做,仍是業務層的處理過程,仍是控制層的處理,都不可避免的會遇到各類可預知的(業務異常主動拋出)、不可預知的異常須要處理。通常dao層、service層的異常都會直接拋出,最後由controller統一進行處理,每一個過程都單獨處理異常,且要考慮到異常信息和前端的反饋,代碼的耦合度高,不統一,後期維護的工做也多。前端
同時還必須考慮異常模塊和日誌模塊、國際化的支持。java
所以須要一種異常處理機制將異常處理解耦出來,這樣保證相關處理過程的功能單一,和系統其它模塊解耦,也實現了異常信息的統一處理和維護。程序員
接下來以實際工做中SpringMVC實現異常的統一處理爲例。web
首先看看SpringMVC處理異常的3中方式,進行比較,最終選用一個比較合適的方式。ajax
對於第一種方式來講,使用SimpleMappingExceptionResolver可以準確顯示定義的異常處理頁面,進行異常處理,具備集成簡單、有良好的擴展性,由於是基於配置的對已有的代碼沒有侵入性等優勢。可是該方法僅僅可以獲取到異常信息,對於其餘數據的狀況不適用。配置方法以下:spring
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 定義默認的異常處理頁面,當該異常類型的註冊時使用 --> <property name="defaultErrorView" value="error"></property> <!-- 定義異常處理頁面用來獲取異常信息的變量名,默認名爲exception --> <property name="exceptionAttribute" value="ex"></property> <!-- 定義須要特殊處理的異常,用類名或徹底路徑名做爲key,異常也頁名做爲值 --> <property name="exceptionMappings"> <props> <prop key="cn.basttg.core.exception.BusinessException">error-business</prop> <prop key="cn.basttg.core.exception.ParameterException">error-parameter</prop> <!-- 這裏還能夠繼續擴展對不一樣異常類型的處理 --> </props> </property> </bean>
對於第二種方式,使用實現HandlerExceptionResolver接口的異常處理進行異常處理,具備集成簡單、良好的擴展性、對已有代碼沒有侵入性等優勢。同時因爲自定義實現,咱們能夠在處理異常時進行額外的處理(日誌的記錄、異常信息的國際化等)。項目實際的開發中也是使用的這種集成方案,配置以下:json
<bean id="exceptionResolver" class="com.***.**.common.exception.PlatformMappingExceptionResolver"> <!--配合自定義的異常解析器--> <property name="exceptionMappings"> <props> <prop key="com.***.**.common.exception.BusinessException">error/error</prop> <prop key="java.lang.Exception">error/error</prop> </props> </property> </bean>
對於第三種方式,經過@ExceptionHandler註解實現異常處理,一樣十分靈活,不過這種方式須要在每一個controller上都需註解,解決方案是增長一個BaseController類,使用@ExceptionHandler註解聲明異常處理,其餘controller都繼承他。實現方式以下:後端
public class BaseController { /** 基於@ExceptionHandler異常處理 */ @ExceptionHandler public String exp(HttpServletRequest request, Exception ex) { request.setAttribute("ex", ex); // 根據不一樣錯誤轉向不一樣頁面 if(ex instanceof BusinessException) { return "error-business"; }else if(ex instanceof ParameterException) { return "error-parameter"; } else { return "error"; } } }
使用這種方法存在侵入性,並且在異常處理時也不能獲取異常之外的數據,且Ajax請求產生的異常信息沒法反饋給前端。app
綜合考慮,使用第二種方式進行異常統一處理方案的設計。異步
首先分析下方案應該實現的需求。
一、 首先自定義異常解析器,代碼清單以下:
package com.cisdi.ecis.common.exception; import java.io.PrintWriter; import java.io.StringWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import com.cisdi.ecis.common.utils.ExceptionI18Message; /** * 平臺異常信息跳轉、解析 */ public class PlatformMappingExceptionResolver extends SimpleMappingExceptionResolver { static Logger logger = LoggerFactory.getLogger(PlatformMappingExceptionResolver.class); @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { String viewName = determineViewName(ex, request); // vm方式返回 if (viewName != null) { if (!( request.getHeader("accept").indexOf("application/json") > -1 || ( request .getHeader("X-Requested-With") != null && request .getHeader("X-Requested-With").indexOf("XMLHttpRequest") > -1 ) )) { // 非異步方式返回 Integer statusCode = determineStatusCode(request, viewName); if (statusCode != null) { applyStatusCodeIfPossible(request, response, statusCode); } // 跳轉到提示頁面 return getModelAndView(viewName, ex, request); } else { // 異步方式返回 try { PrintWriter writer = response.getWriter(); writer.write(ExceptionI18Message.getLocaleMessage(ex.getMessage())); response.setStatus(404, ExceptionI18Message.getLocaleMessage(ex.getMessage())); //將異常棧信息記錄到日誌中 logger.error(getTrace(ex)); writer.flush(); } catch ( Exception e ) { e.printStackTrace(); } // 不進行頁面跳轉 return null; } } else { return null; } } public static String getTrace(Throwable t) { StringWriter stringWriter= new StringWriter(); PrintWriter writer= new PrintWriter(stringWriter); t.printStackTrace(writer); StringBuffer buffer= stringWriter.getBuffer(); return buffer.toString(); } }
二、以後在SpringMVC配置文件中配置異常解析器映射路徑。
<!--配置異常映射路徑,ajax提示 --> <bean id="exceptionResolver" class="com.cisdi.ecis.common.exception.PlatformMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="com.cisdi.ecis.common.exception.BusinessException">error/error</prop> <prop key="java.lang.Exception">error/error</prop> </props> </property> </bean>
三、 異常信息的國際化
經過上述配置其實就已經知足了方案需求中的大部分需求,還僅剩一個需求:異常信息的國際化。上述代碼中有一段代碼:
ExceptionI18Message.getLocaleMessage(ex.getMessage()
ExceptionI18Message就是根據當前的語言環境獲得異常信息,實現細則以下:
package com.cisdi.ecis.common.utils; import javax.servlet.http.HttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.support.RequestContext; public class ExceptionI18Message{ public static String getLocaleMessage(String key){ HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); RequestContext requestContext = new RequestContext(request); return requestContext.getMessage(key); } }
那麼,全局異常處理器寫好後,若是使用呢?後端程序員在編碼時,能夠直接拋出業務異常,可是壓入的message應該是國際化文件中的"key",本身在去國際化文件中編寫多套語言的key的value。例如:
##Exception pbs.exception.copyNode=The Exceptioninfo I18n
以後咱們壓入的異常信息爲pbs.exception.copyNode:
throw new Exception("pbs.exception.copyNode");
到此爲止,方案已經設計完畢,簡單的測試下是否知足咱們的需求吧,對於頁面跳轉的異常這裏就不在測試了,主要在於前端Ajax請求controller拋出業務異常的時候前端是否可以收到反饋。
前端代碼:
$.ajax({ url: "${basePath}/doc/addDocMaterials", type: "post", dataType: "json", data: obj, complete: function(xhr) { console.log(xhr); if (xhr.status == 200 && xhr.responseText != null) {} else { $.messager.alert('#springMessage("message.tip")', xhr.responseText); displayLoad(); } } });
以後後端主動拋出業務異常的時候,前端獲取到的反饋結果以下:(這裏咱們就以上面的拋出異常的代碼爲例)。
![]() |
到此爲止,關於SpringMVC異常的統一處理方案(國際化、Ajax反饋)結束。