1. 程式人生 > >Spring MVC 統一異常處理的兩種方式

Spring MVC 統一異常處理的兩種方式

沒有廢話,直接來。

方式一
通過@ControllerAdvice 和 @ExceptionHandler 方法。

@ControllerAdvice 這個註解,可以將對於控制器的全域性配置放到註解了@ControllerAdvice的類上,它結合了 @Component 所以可以自動註冊為bean
註解了@Controller的類的方法可使用 @ExceptionHandler @InitBinder @MoudelAttribute
註解到方法上
以上這些對所有註解了@requestMapping的方法有效
@ExceptionHandler 用於全域性處理控制器的異常
@InitBinder 用來設定WebDataBinder ,WebDataBinder 用來自動繫結前臺請求引數到Model
@MoudelAttribute 作用是繫結鍵值對到Model中,此處是讓全域性的@RequestMapping都能獲取到此處設定的鍵值對。

看到這裡發現@ControllerAdvice其實對異常處理並沒有什麼幫助,但是如果@ExceptionHandler 不配合 @ControllerAdvice使用,有一種處理方案,寫一個BaseController然後 寫一個有@ExceptionHandler的方法,然後所有需要的Controller都繼承BaseController,很明顯這樣很麻煩而且程式碼入侵性太強。差評!

上程式碼:

package com.wugz.app.controller.exception;

import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

import com.wugz.app.utils.BusinessException;


/*
通過@ControllerAdvice,可以對於控制器的全域性配置放在同一位置,
註解了@Controller的類的方法可使用 @ExceptionHandler @InitBinder @MoudelAttribute
註解到方法上
以上這些對所有註解了@requestMapping的方法有效
@ExceptionHandler 用於全域性處理控制器的異常
@InitBinder 用來設定WebDataBinder ,WebDataBinder 用來自動繫結前臺請求引數到Model
@MoudelAttribute 作用是繫結鍵值對到Model中,此處是讓全域性的@RequestMapping都能獲取到此處設定的鍵值對
*/

//宣告這是一個控制器 建言 @ControllerAdvice 結合了 @Component 所以可以自動註冊為bean ,如果這個類只有  @Component 則@ExceptionHandler 不生效
@ControllerAdvice 
public class ExceptionHandlerAdvice {
	
	/***
	 * 
	 * @Description(功能描述)    :  集中處理@requestMapping中丟擲的  Exception 異常
	 * @author(作者)             :  吳桂鎮
	 * @date (開發日期)          :  2017年11月30日 下午3:01:06 
	 * @exception                : 
	 * @param ex
	 * @param request
	 * @return  Object
	 */
	@ExceptionHandler(value=Exception.class) //不設定value 則攔截所有的 Exception
	@ResponseBody
	public Object exception(Exception ex,WebRequest request) {
		System.out.println("exception");
		ModelAndView mv = new ModelAndView();
		mv.addObject(123);
		return "return Exception";
	}
	
	/***
	 * 
	 * @Description(功能描述)    :  處理@requestMapping中丟擲的 業務異常
	 * @author(作者)             :  吳桂鎮
	 * @date (開發日期)          :  2017年11月30日 下午3:01:31 
	 * @exception                : 
	 * @param ex
	 * @param request
	 * @return  Object
	 */
	@ExceptionHandler(value=BusinessException.class)
	@ResponseBody
	public Object businessException(Exception ex,WebRequest request) {
		System.out.println("businessException");
		return "return BusinessException";
	}
	
	@ModelAttribute
	public void addAttribute(Model model) {
		System.out.println("addAttribute");
	}
	
	@InitBinder
	public void initBinder(WebDataBinder binder) {
		System.out.println("initBinder");
	}
	
}

方式二 實現HandlerExceptionResolver
這個就不廢話直接上程式碼了

package com.wugz.app.controller.exception;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONObject;
import com.wugz.app.utils.BusinessException;
/**
 * 
  * @ClassName(類名)      : MyExceptionHandler
  * @Description(描述)    : 通過實現HandlerExceptionResolver 也可以進行統一的異常處理 
  * 					  ,比較這種方式 增加了對目標方法相關資訊的獲取,可以根據這些資訊進行相關的記錄
  * @author(作者)         :吳桂鎮
  * @date (開發日期)      :2017年11月30日 下午3:23:27
  *
 */
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {

	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		ModelAndView mv = new ModelAndView();
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		Method method = handlerMethod.getMethod();
		Annotation an = method.getAnnotation(ResponseBody.class);
		JSONObject result = new JSONObject();
		
		//異常方法沒有@ResponseBody 註釋 直接返回頁面將錯誤資訊
		if(an == null) { 
			//處理異常
			resolverException(ex, result);
			//模擬跳轉到處理頁面
			mv.setViewName("index"); 
			//將異常資訊放置到前臺 接受程式碼示例 ${requestScope.errorMessage.msg} 或者 
			//Object msg =  request.getAttribute("errorMessage"); 然後在 js中 var result = '<%=msg%>'; 此時的result 是一個json格式的字串
			mv.addObject("errorMessage", result);
			
		//異常方法有@ResponseBody 則返回json 字串 
		}else { 
			//處理異常
			resolverException(ex, result);
			//將返回資訊寫到 ResponseBody中 不跳轉頁面 就像 使用了 @ResponseBody
	        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
	        response.setCharacterEncoding("UTF-8");
	        response.setHeader("Cache-Control", "no-cache, must-revalidate");  
	        try {
	            response.getWriter().write(result.toJSONString());
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
		}
		return mv;
	}
	
	//處理異常
	private void resolverException(Exception ex, JSONObject result) {
		
		if(ex instanceof BusinessException) {
			resolverBussinessException(ex, result);
		}  else {
			resolverOtherException(ex, result); 
		}
	}

	/*
     * 處理業務層異常 
     */
    private void resolverBussinessException(Exception ex,  JSONObject result) {
        //BusinessException businessException = (BusinessException) ex;
        result.put("msg", "業務異常");
    }
    
    /*
     * 處理其他異常
     */
    private void resolverOtherException(Exception ex, JSONObject result) {
        result.put("msg", "系統異常");
    }

}

ps:一般來講這兩種方式沒啥不同,都比較簡單
第一種因為會寫到統一Controller配置的類中,相對來講配置的管理相對集中一點。
第二種因為實現了HandlerExceptionResolver 可以獲取到handler,這個Object 的 handler 可以強制轉換成HandlerMethod,這個HandlerMethod就是原始碼裡HandlerMapping傳給HandlerAdapter進行引數解析的重要因素,通過他可以獲取很多資訊,這一點優勢是第一種方法沒有的!

注:修改後的程式碼

@ExceptionHandler(value=Exception.class) //不設定value 則攔截所有的 Exception
	@ResponseBody
	public Object exception(Exception ex,WebRequest request,HandlerMethod handler) {
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		System.out.println("exception");
		ModelAndView mv = new ModelAndView();
		mv.addObject(123);
		return "return Exception";
	}

可以發現增加引數HandlerMethod spring也可以幫我們賦值,可以說這兩種的區別很小了。。。。


補充: 其實還是有些區別的,具體看程式碼註釋吧,所以說如果需要到HandlerMethod 獲取資訊,最好的方式還是實現HandlerExceptionResolver,另外在使用HandlerMethod 的時候一定判斷是否為空

/***
	 * 
	 * @Description(功能描述)    :  集中處理@requestMapping中丟擲的  Exception 異常 ,如果有些異常在 HandlerMethod 為空的時候就丟擲了
	 * 							 比如上傳檔案大小超出限制的 MaxUploadSizeExceededException HandlerMethod為空 則不會進入當前的異常捕獲方法
	 * 							如果去掉引數HandlerMethod 則會進入 例如nohandlerException 
	 * @author(作者)             :  
	 * @date (開發日期)          :  2017年11月30日 下午3:01:06 
	 * @exception                : 
	 * @param ex
	 * @param request
	 * @param HandlerMethod  可以獲取到異常方法的相關資訊,利用反射
	 * @return  Object
	 */
	@ExceptionHandler(value=Exception.class) //不設定value 則攔截所有的 Exception
	@ResponseBody
	public Object exception(Exception ex,WebRequest request,HandlerMethod handler) {
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		System.out.println("exception");
		ModelAndView mv = new ModelAndView();
		mv.addObject(123);
		return "return Exception";
	}
	
	@ExceptionHandler(value=Exception.class) //不設定value 則攔截所有的 Exception
	@ResponseBody
	public Object nohandlerException(Exception ex,WebRequest request) {
		System.out.println("nohandlerException");
		ModelAndView mv = new ModelAndView();
		mv.addObject(123);
		return "return nohandlerException";
	}