關于Spring統(tǒng)一異常處理及說明
相關注解概述
通過使用@RestControllerAdvice或@ControllerAdvice定義統(tǒng)一的異常處理類,而不是在每個Controller中逐個定義;
@RestControllerAdvice
:返回Json格式數(shù)據(jù);@ControllerAdvice
:返回視圖類型數(shù)據(jù);@ResponseBody
:和Controller方法上的用法一樣,會將方法中返回值轉換成Json格式后返回給客戶端;@ExceptionHandler
:用來定義函數(shù)針對的異常類型,最后將Exception對象和請求URL映射到返回結果中;用于注釋異常處理類,value屬性指定需要攔截的異常類型。- spring應用啟動后,被@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法,都會作用在被@RequestMapping注解的方法上。
- 全局捕獲異常的原理:使用AOP切面技術。
- 用@RequestBody,就解決了JSon自動綁定。
全局異常依賴包
由于全局捕獲異常使用的是AOP切面技術,需要直接或間接導入aop的jar包。
<dependency> ?? ? ?<groupId>org.springframework.boot</groupId> ?? ? ?<artifactId>spring-boot-starter-aop</artifactId> </dependency>
示例
import com.terse.develop.utils.exception.AdminException; import com.terse.develop.utils.exception.MsgResultException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.io.IOException; import java.net.ConnectException; import java.util.List; import java.util.Set; /** ?* 全局異常處理 ?*/ @RestControllerAdvice @ResponseBody public class ExceptionHandlerController { ? ? private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlerController1.class); ? ? public static void print(Exception ex) { ? ? ? ? logger.error(ex.toString()); ? ? ? ? ex.printStackTrace(); ? ? } ? ? //Http請求方式不支持 ? ? @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) ? ? public Exception HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { ? ? ? ? print(e); ? ? ? ? return new Exception("Http請求方式不支持"); ? ? } ?? ?/** ? ? ?* MsgResultException、AdminException 均為自定義異常類 ? ? ?*/ ? ? @ExceptionHandler(value = {MsgResultException.class, AdminException.class}) ? ? public Exception exception(Exception e) { ? ? ? ? print(e); ? ? ? ? if (e instanceof MsgResultException) ? ? ? ? ? ? //捕獲什么異常信息就返回給前端什么信息 ? ? ? ? ? ? return new MsgResultException(((MsgResultException) e).getMsg()); ? ? ? ? else ? ? ? ? ? ? //只返回給后端"請重試" ? ? ? ? ? ? return new AdminException("請重試"); ? ? } ? ? //request Body 讀取異常 ? ? @ExceptionHandler(value = HttpMessageNotReadableException.class) ? ? public Exception httpMessageNotReadableException(HttpMessageNotReadableException e) { ? ? ? ? print(e); ? ? ? ? return new Exception("請檢查數(shù)據(jù)是否正確"); ? ? } ? ? //參數(shù)類型轉換異常 ? ? @ExceptionHandler(value = MethodArgumentTypeMismatchException.class) ? ? public Exception getNumberException(MethodArgumentTypeMismatchException e) { ? ? ? ? print(e); ? ? ? ? return new Exception("String類型不能轉換成數(shù)字類型"); ? ? } ? ? /** ? ? ?* 校驗基本類型 ? ? ?*/ ? ? @ExceptionHandler(value = ConstraintViolationException.class) ? ? public Exception ConstraintViolationExceptionHandler(ConstraintViolationException ex) { ? ? ? ? Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations(); ? ? ? ? String msg = null; ? ? ? ? for (ConstraintViolation<?> cvl : constraintViolations) { ? ? ? ? ? ? msg = cvl.getMessageTemplate(); ? ? ? ? } ? ? ? ? logger.error(msg); ? ? ? ? return new Exception(msg); ? ? } ? ? //缺少參數(shù)異常 ? ? @ExceptionHandler(value = ServletRequestBindingException.class) ? ? public Exception ServletRequestBindingException(ServletRequestBindingException e) { ? ? ? ? print(e); ? ? ? ? return new Exception("缺少參數(shù)"); ? ? } ? ? /** ? ? ?* 校驗對象類型 ? ? ?*/ ? ? @ExceptionHandler(value = BindException.class) ? ? public Exception BindExceptionHandler(BindException bindException) { ? ? ? ? List<FieldError> fieldErrors = bindException.getFieldErrors(); ? ? ? ? String msg = null; ? ? ? ? for (FieldError fieldError : fieldErrors) { ? ? ? ? ? ? msg = fieldError.getDefaultMessage(); ? ? ? ? } ? ? ? ? return new Exception(msg); ? ? } ? ? //請求異常 ? ? @ExceptionHandler(value = ConnectException.class) ? ? public Exception connectException(ConnectException e) { ? ? ? ? print(e); ? ? ? ? return new Exception("服務器異常,請聯(lián)系管理員"); ? ? } ? ? //IO流異常 ? ? @ExceptionHandler(value = IOException.class) ? ? public Exception ioException(IOException e) { ? ? ? ? e.printStackTrace(); ? ? ? ? return new Exception("IO流處理異常,請聯(lián)系管理員"); ? ? } }
@ExceptionHandler注解使用方法
基本使用方法
Spring的@ExceptionHandler可以用來統(tǒng)一處理方法拋出的異常
比如這樣:
@ExceptionHandler() public String handleExeption2(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:默認"; ? ? return resultStr; }
當我們使用這個@ExceptionHandler注解時,我們需要定義一個異常的處理方法,比如上面的handlerException2()方法,給這個方法加上@ExceptionHandler注解,這個方法就好處理被@RequestMapping注解方法拋出的異常。
注解的參數(shù)
@ExceptionHandler注解中可以添加參數(shù),參數(shù)是某個異常類的class,代表這個方法專門處理該類異常
比如:
@ExceptionHandler(NumberFormatException.class) public String handleExeption(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:NumberFormatException"; ? ? return resultStr; }
此時注解的參數(shù)是NumberFormatException.class,表示只有方法拋出NumberFormatException時,才會調用該方法。
異常類型就近原則
當異常發(fā)生時,Spring會選擇最接近拋出異常類型的處理方法。
比如之前提到的NumberFormatException,這個異常有父類RuntimeException,RuntimeException還有父類Exception,如果我們分別定義異常處理方法,@ExceptionHandler分別使用這三個異常作為參數(shù)
比如:
@ExceptionHandler(NumberFormatException.class) public String handleExeption(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:NumberFormatException"; ? ? return resultStr; } ? @ExceptionHandler() public String handleExeption2(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:默認"; ? ? return resultStr; } ? @ExceptionHandler(RuntimeException.class) public String handleExeption3(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:RuntimeException"; ? ? return resultStr; }
那么,當代碼拋出NumberFormatException時,調用的方法將是注解參數(shù)NumberFormatException.class的方法,也就是handleExeption(),而當代碼拋出IndexOutOfBoundsException時,調用的方法將是注解參數(shù)RuntimeException的方法,也就是handleExeption3()。
注解方法的返回值
標識了@ExceptionHandler注解的方法,返回值類型和標識了@RequestMapping的方法是統(tǒng)一的,可參見@RequestMapping的說明,比如默認返回Spring的ModelAndView對象,也可以返回String,這時的String是ModelAndView的路徑,而不是字符串本身。
有些情況下我們會給標識了@RequestMapping的方法添加@ResponseBody,比如使用Ajax的場景,直接返回字符串,異常處理類也可以如此操作,添加@ResponseBody注解后,可以直接返回字符串
比如這樣:
@ExceptionHandler(NumberFormatException.class) @ResponseBody public String handleExeption(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:NumberFormatException"; ? ? return resultStr; }
這樣的操作可以在執(zhí)行完方法后直接返回字符串本身。
注解使用錯誤舉例
使用@ExceptionHandler時盡量不要使用相同的注解參數(shù)。
如果我們定義兩個處理相同異常的處理方法:
@ExceptionHandler(NumberFormatException.class) @ResponseBody public String handleExeption(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:NumberFormatException"; ? ? return resultStr; } ? @ExceptionHandler(NumberFormatException.class) @ResponseBody public String handleExeption2(Exception ex) { ? ? System.out.println("拋異常了:" + ex); ? ? ex.printStackTrace(); ? ? String resultStr = "異常:默認"; ? ? return resultStr; }
兩個方法都處理NumberFormatException,這種定義方式編譯可以通過,而當NumberFormatException真正被拋出時
Spring會給我們報錯:
java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class java.lang.NumberFormatException]: {public java.lang.String TestController.handleExeption(java.lang.Exception), public java.lang.String TestController.handleExeption2(java.lang.Exception)}
at org.springframework.web.method.annotation.ExceptionHandlerMethodResolver.addExceptionMapping(ExceptionHandlerMethodResolver.java:102) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
...
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
如何在SpringBoot+Freemarker中獲取項目根目錄
這篇文章主要介紹了如何在SpringBoot+Freemarker中獲取項目根目錄的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10springboot使用spring-data-jpa操作MySQL數(shù)據(jù)庫
這篇文章主要介紹了springboot使用spring-data-jpa操作MySQL數(shù)據(jù)庫,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07如何用注解的方式實現(xiàn)Mybatis插入數(shù)據(jù)時返回自增的主鍵Id
這篇文章主要介紹了如何用注解的方式實現(xiàn)Mybatis插入數(shù)據(jù)時返回自增的主鍵Id,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07MyBatis實現(xiàn)簡單的數(shù)據(jù)表分月存儲
本文主要介紹了MyBatis實現(xiàn)簡單的數(shù)據(jù)表分月存儲,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-03-03