本文概述
本文將完成一個(gè) springboot 中異常處理的小 demo承绸,將使用 try-catch 和@RestControllerAdvice 兩種方法。本文代碼地址挣轨。
demo 概述
需求很簡(jiǎn)單訪問(wèn) http://localhost:8080/{id}
接口军熏,如果id是3則拋出用戶不存在的異常,如果id小于等于0則拋出參數(shù)異常刃唐。為了方便閱讀羞迷,我們先定義一些類和函數(shù)界轩。
定義HelloController
@RestController
public class HelloController {
@Autowired
private UserInfoManager userInfoManager;
@GetMapping("/{id}")
public String index(@PathVariable("id") long id) {
return userInfoManager.getUserById(id);
}
}
定義 ServiceException画饥,這是所有自定義異常的父類。
public class ServiceException extends RuntimeException {
private int statusCode;
private ServiceException.ErrorType errorType;
private String code;
public enum ErrorType {
Client,
Service,
Unknown
}
}
定義 ResourceNotFoundException
public class ResourceNotFoundException extends ServiceException {
public ResourceNotFoundException(String message) {
super(message);
this.setStatusCode(HttpStatus.NOT_FOUND.value());
this.setCode("USER_INFO_NOT_FOUND");
this.setErrorType(ErrorType.Client);
}
}
在 UserInfoManager 中浊猾,如果查找的用戶的id是3我們拋出 ResourceNotFoundException 的異常抖甘。
@Component
public class UserInfoManager {
public String getUserById(long id) {
if (id == 3L) {
throw new ResourceNotFoundException("user 3 is not found");
}
return "This is user " + id;
}
}
使用try-catch處理異常
此時(shí),我們?cè)L問(wèn) http://localhost:8080/3
葫慎,會(huì)得到以下的 response衔彻,這是spring 自帶的 response。
{
"timestamp": "2021-03-03T13:24:21.217+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/3"
}
如何修改 status 呢偷办?很簡(jiǎn)單艰额,加上注釋 @ResponseStatus(code = xxx)
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends ServiceException {
public ResourceNotFoundException(String message) {
super(message);
this.setStatusCode(HttpStatus.NOT_FOUND.value());
this.setCode("USER_INFO_NOT_FOUND");
this.setErrorType(ErrorType.Client);
}
}
再次訪問(wèn)接口,我們得到了以下的 response椒涯。
{
"timestamp": "2021-03-03T13:28:50.473+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/3"
}
springboot 自帶的異常處理返回的 response 往往不能滿足我們的需求柄沮,如何產(chǎn)生自己的 response 呢?首先是一種比較傳統(tǒng)的方法废岂,我們使用try-catch 在 HelloController 中抓住 ResourceNotFoundException 異常祖搓,并返回一個(gè) ResponseEntity,其body內(nèi)容是我們定義的一個(gè)處理錯(cuò)誤返回的類 ErrorResponse湖苞。
public class ErrorResponse {
private String code;
private String message;
private int statusCode;
private ServiceException.ErrorType errorType;
}
@RestController
public class HelloController {
@Autowired
private UserInfoManager userInfoManager;
@GetMapping("/{id}")
public ResponseEntity<?> index(@PathVariable("id") long id) {
try {
return ResponseEntity.ok(userInfoManager.getUserById(id));
} catch (ResourceNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setCode(ex.getCode());
errorResponse.setErrorType(ex.getErrorType());
errorResponse.setMessage(ex.getMessage());
errorResponse.setStatusCode(ex.getStatusCode());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(errorResponse);
}
}
}
再次訪問(wèn)接口拯欧,返回了我們想要的結(jié)果。但是這種 try-catch 的方法非常麻煩财骨,而且代碼非常的丑镐作。
{
"code": "USER_INFO_NOT_FOUND",
"message": "user 3 is not found",
"statusCode": 404,
"errorType": "Client"
}
使用 @RestControllerAdvice 進(jìn)行異常統(tǒng)一處理
我們使用注解 @RestControllerAdvice 對(duì) controller 進(jìn)行增強(qiáng),使用 @ExceptionHandler(Class) 對(duì)異常進(jìn)行處理隆箩。其實(shí)函數(shù)內(nèi)容和上文中try-catch代碼塊是一摸一樣的该贾。只是 springboot 給我們帶來(lái)了更為方便的操作,他將 controller 進(jìn)行增強(qiáng)摘仅,代我們處理了所有異常靶庙。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
ResponseEntity<?> handleResourceNotFoundException(ResourceNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setCode(ex.getCode());
errorResponse.setErrorType(ex.getErrorType());
errorResponse.setMessage(ex.getMessage());
errorResponse.setStatusCode(ex.getStatusCode());
return ResponseEntity.status(ex.getStatusCode())
.body(errorResponse);
}
}
此時(shí)的 HelloConroller也變得異常簡(jiǎn)單。
@RestController
public class HelloController {
@Autowired
private UserInfoManager userInfoManager;
@GetMapping("/{id}")
public ResponseEntity<?> index(@PathVariable("id") long id) {
return ResponseEntity.ok(userInfoManager.getUserById(id));
}
}
如果還有其他的異常,我們還需要寫很多個(gè)帶有 @ExceptionHandler 的函數(shù)嗎六荒?DO NOT REPEAT YOURSELF护姆!很顯然是不需要的。任何需要子類的地方掏击,我們都可以傳他的父類卵皂。所以我們所有的自定義異常都只要繼承 ServiceException 就好了。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
ResponseEntity<?> handleServiceException(ServiceException ex) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setCode(ex.getCode());
errorResponse.setErrorType(ex.getErrorType());
errorResponse.setMessage(ex.getMessage());
errorResponse.setStatusCode(ex.getStatusCode());
return ResponseEntity.status(ex.getStatusCode())
.body(errorResponse);
}
}
測(cè)試一下吧砚亭,我們定義一個(gè)新的異常 InvalidParameterException灯变。
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public class InvalidParameterException extends ServiceException {
public InvalidParameterException(String message) {
super(message);
this.setStatusCode(HttpStatus.BAD_REQUEST.value());
this.setErrorType(ErrorType.Client);
this.setCode("INVALID_PARAMETER");
}
}
在用戶id小于等于0的時(shí)候拋出 InvalidParameterException,修改 UserInfoManager捅膘。
@Component
public class UserInfoManager {
public String getUserById(long id) {
if (id == 3L) {
throw new ResourceNotFoundException("user 3 is not found");
}
if (id <= 0L) {
throw new InvalidParameterException(String.format("user %s is invalid", id));
}
return "This is user " + id;
}
}
訪問(wèn)一個(gè)異常接口 http://localhost:8080/0
添祸,得到了想要的返回。拋出異逞罢蹋可以在 manage 層刃泌,也可以在 controller 層,這是無(wú)所謂的署尤,因?yàn)楫惓?huì)自下而上往上拋出耙替,最終都會(huì)在 controller 層被發(fā)現(xiàn)并處理。
{
"code": "INVALID_PARAMETER",
"message": "user 0 is invalid",
"statusCode": 400,
"errorType": "Client"
}
完曹体。