SpringMVC異常統一處理(異常信息的國際化,日誌記錄)

       JAVA EE項目中,不論是對底層的數據操做,仍是業務層的處理過程,仍是控制層的處理,都不可避免的會遇到各類可預知的(業務異常主動拋出)、不可預知的異常須要處理。通常dao層、service層的異常都會直接拋出,最後由controller統一進行處理,每一個過程都單獨處理異常,且要考慮到異常信息和前端的反饋,代碼的耦合度高,不統一,後期維護的工做也多。前端

       同時還必須考慮異常模塊和日誌模塊、國際化的支持。java

       所以須要一種異常處理機制將異常處理解耦出來,這樣保證相關處理過程的功能單一,和系統其它模塊解耦,也實現了異常信息的統一處理和維護。程序員

       接下來以實際工做中SpringMVC實現異常的統一處理爲例。web

分析

       首先看看SpringMVC處理異常的3中方式,進行比較,最終選用一個比較合適的方式。ajax

  1.   SpringMVC提供的簡單異常處理器SimpleMappingExceptionResolver;
  2.   SpringMVC異常處理接口HandlerExceptionResolver自定義本身的異常處理器;
  3.   @ExceptionHandler註解實現異常處理;

簡單實踐

      對於第一種方式來講,使用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

      綜合考慮,使用第二種方式進行異常統一處理方案的設計。異步

方案設計

      首先分析下方案應該實現的需求。

需求

  1.   出錯頁面跳轉: 例如404頁面。基於SpringMVC,前端訪問某個頁面跳轉controller的時候出現異常的時候,跳轉到錯誤頁面。
  2.   Ajax異常反饋: 前端經過Ajax的方式訪問controller獲取JSON數據出現異常的時候,須要將異常信息反饋給前端。
  3.   異常信息的日誌記錄: 配合日誌模塊,實現異常日誌的記錄。
  4.   異常信息的國際化:  配合國際化設計實現異常信息的國際化。

設計

      一、 首先自定義異常解析器,代碼清單以下:

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反饋)結束。

相關文章
相關標籤/搜索