SpringBoot中如何統(tǒng)一接口返回與全局異常處理詳解
背景
在分布式、微服務盛行的今天,絕大部分項目都采用的微服務框架,前后端分離方式。前端和后端進行交互,前端按照約定請求URL路徑,并傳入相關參數(shù),后端服務器接收請求,進行業(yè)務處理,返回數(shù)據(jù)給前端。維護一套完善且規(guī)范的接口是非常有必要的, 這樣不僅能夠提高對接效率,也可以讓我的代碼看起來更加簡潔優(yōu)雅。
使用統(tǒng)一返回結果時,還有一種情況,就是程序的報錯是由于運行時異常導致的結果,有些異常是我們在業(yè)務中拋出的,有些是無法提前預知。
因此,我們需要定義一個統(tǒng)一的全局異常,在Controller捕獲所有異常,并且做適當處理,并作為一種結果返回。
統(tǒng)一接口返回
定義API返回碼枚舉類
public enum ResultCode {
/* 成功狀態(tài)碼 */
SUCCESS(200, "成功"),
/* 錯誤狀態(tài)碼 */
NOT_FOUND(404, "請求的資源不存在"),
INTERNAL_ERROR(500, "服務器內(nèi)部錯誤"),
PARAMETER_EXCEPTION(501, "請求參數(shù)校驗異常"),
/* 業(yè)務狀態(tài)碼 */
USER_NOT_EXIST_ERROR(10001, "用戶不存在"),
;
private Integer code;
private String message;
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}
定義正常響應的API統(tǒng)一返回體
@Data
public class Result<T> implements Serializable {
private Integer code;
private String message;
private boolean success = true;
private T data;
@JsonIgnore
private ResultCode resultCode;
private Result() {
}
public void setResultCode(ResultCode resultCode) {
this.resultCode = resultCode;
this.code = resultCode.code();
this.message = resultCode.message();
}
public Result(ResultCode resultCode, T data) {
this.code = resultCode.code();
this.message = resultCode.message();
this.data = data;
}
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
return result;
}
}
定義異常響應的API統(tǒng)一返回體
@Data
public class ErrorResult implements Serializable {
private Integer code;
private String message;
private boolean success = false;
@JsonIgnore
private ResultCode resultCode;
public static ErrorResult error() {
ErrorResult result = new ErrorResult();
result.setResultCode(ResultCode.INTERNAL_ERROR);
return result;
}
public static ErrorResult error(String message) {
ErrorResult result = new ErrorResult();
result.setCode(ResultCode.INTERNAL_ERROR.code());
result.setMessage(message);
return result;
}
public static ErrorResult error(Integer code, String message) {
ErrorResult result = new ErrorResult();
result.setCode(code);
result.setMessage(message);
return result;
}
public static ErrorResult error(ResultCode resultCode, String message) {
ErrorResult result = new ErrorResult();
result.setResultCode(resultCode);
result.setMessage(message)
return result;
}
}
編寫包裝返回結果的自定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD}) //作用于方法和類(接口)上
@Documented
public @interface ResponseResult {
}
定義返回結果攔截器
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
/* 使用統(tǒng)一返回體的標識 */
private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 正在處理請求的方法bean
if (handler instanceof HandlerMethod) {
final HandlerMethod handlerMethod = (HandlerMethod) handler;
// 獲取當前類
final Class<?> clazz = handlerMethod.getBeanType();
// 獲取當前方法
final Method method = handlerMethod.getMethod();
// 判斷是否在類對象上加了注解
if (clazz.isAnnotationPresent(ResponseResult.class)) {
// 設置該請求返回體,需要包裝,往下傳遞,在ResponseBodyAdvice接口進行判斷
request.setAttribute(RESPONSE_RESULT_ANNOTATION, clazz.getAnnotation(ResponseResult.class));
}
// 判斷是否在方法上加了注解
else if (method.isAnnotationPresent(ResponseResult.class)) {
// 設置該請求返回體,需要包裝,往下傳遞,在ResponseBodyAdvice接口進行判斷
request.setAttribute(RESPONSE_RESULT_ANNOTATION, method.getAnnotation(ResponseResult.class));
}
}
return true;
}
}
WebMvc配置類攔截器注冊者添加返回結果攔截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 添加自定義攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/**");
}
}
編寫響應體處理器
/**
* 統(tǒng)一處理響應體,用Result.success靜態(tài)方法包裝,
* 在API接口使用時就可以直接返回原始類型
*/
@RestControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
/* 使用統(tǒng)一返回體的標識 */
private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(sra).getRequest();
ResponseResult responseResult = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANNOTATION);
// 判斷返回體是否需要處理
return responseResult != null;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 異常響應體則直接返回code+message的消息體
if (body instanceof ErrorResult) {
return body;
}
// 正常響應體則返回Result包裝的code+message+data的消息體
return Result.success(body);
}
}
接口調(diào)用
@Api("用戶管理")
@RestController
@RequestMapping("user")
@ResponseResult // 作用于類上,對所有接口有效
public class UserController {
@Autowired
private UserService userService;
@ResponseResult // 作用于方法上
@ApiOperation("根據(jù)ID查詢用戶")
@GetMapping("one")
public User selectOne(Long id) {
// 由于在ResponseResultHandler中已經(jīng)統(tǒng)一將返回數(shù)據(jù)用Result.success包裝了,
// 直接返回原始類型即可,代碼更簡潔
return this.userService.queryById(id);
}
@ResponseResult
@ApiOperation("查詢所有用戶")
@GetMapping("all")
public List<User> selectAll(Page page) {
// 由于在ResponseResultHandler中已經(jīng)統(tǒng)一將返回數(shù)據(jù)用Result.success包裝了,
// 直接返回原始類型即可,代碼更簡潔
return this.userService.queryAllByLimit(page);
}
}
測試結果


全局異常處理
編寫自定義異?;?/h3>
@Data
public class BaseException extends RuntimeException {
private static final int BASE_EXCEPTION_CODE = ResultCode.INTERNAL_ERROR.code();
private static final String BASE_EXCEPTION_MESSAGE = ResultCode.INTERNAL_ERROR.message();
private Integer code;
private String message;
public BaseException() {
super(BASE_EXCEPTION_MESSAGE);
this.code = BASE_EXCEPTION_CODE;
this.message = BASE_EXCEPTION_MESSAGE;
}
public BaseException(String message) {
super(message);
this.code = BASE_EXCEPTION_CODE;
this.message = message;
}
public BaseException(ResultCode resultCode) {
super(resultCode.message());
this.code = resultCode.code();
this.message = resultCode.message();
}
public BaseException(Throwable cause) {
super(cause);
this.code = BASE_EXCEPTION_CODE;
this.message = BASE_EXCEPTION_MESSAGE;
}
public BaseException(String message, Throwable cause) {
super(message, cause);
this.code = BASE_EXCEPTION_CODE;
this.message = message;
}
public BaseException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BaseException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
}
@Data
public class BaseException extends RuntimeException {
private static final int BASE_EXCEPTION_CODE = ResultCode.INTERNAL_ERROR.code();
private static final String BASE_EXCEPTION_MESSAGE = ResultCode.INTERNAL_ERROR.message();
private Integer code;
private String message;
public BaseException() {
super(BASE_EXCEPTION_MESSAGE);
this.code = BASE_EXCEPTION_CODE;
this.message = BASE_EXCEPTION_MESSAGE;
}
public BaseException(String message) {
super(message);
this.code = BASE_EXCEPTION_CODE;
this.message = message;
}
public BaseException(ResultCode resultCode) {
super(resultCode.message());
this.code = resultCode.code();
this.message = resultCode.message();
}
public BaseException(Throwable cause) {
super(cause);
this.code = BASE_EXCEPTION_CODE;
this.message = BASE_EXCEPTION_MESSAGE;
}
public BaseException(String message, Throwable cause) {
super(message, cause);
this.code = BASE_EXCEPTION_CODE;
this.message = message;
}
public BaseException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BaseException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
}
編寫自定義業(yè)務異常類
public class BizException extends BaseException {
public BizException(ResultCode resultCode) {
super(resultCode);
}
}
定義全局異常處理類
通過@ExceptionHandler注解來統(tǒng)一處理某一類異常
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 統(tǒng)一處理自定義基礎異常
*/
@ExceptionHandler(BaseException.class)
public ErrorResult baseException(BaseException e) {
if (StringUtils.isEmpty(e.getCode())) {
return ErrorResult.error(e.getMessage());
}
return ErrorResult.error(e.getCode(), e.getMessage());
}
/**
* 統(tǒng)一處理自定義業(yè)務異常
*/
@ExceptionHandler(BizException.class)
public ErrorResult bizException(BizException e) {
if (StringUtils.isEmpty(e.getCode())) {
return ErrorResult.error(e.getMessage());
}
return ErrorResult.error(e.getCode(), e.getMessage());
}
/**
* 統(tǒng)一處理非自定義異常外的所有異常
*/
@ExceptionHandler(Exception.class)
public ErrorResult handleException(Exception e) {
log.error(e.getMessage(), e);
return ErrorResult.error(e.getMessage());
}
/**
* 兼容Validation校驗框架:忽略參數(shù)異常處理器
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ApiResult<String> parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
log.error(e.getMessage(), e);
return ErrorResult.error(PARAMETER_EXCEPTION, "請求參數(shù) " + e.getParameterName() + " 不能為空");
}
/**
* 兼容Validation校驗框架:缺少請求體異常處理器
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ErrorResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
log.error(e.getMessage(), e);
return ErrorResult.error(PARAMETER_EXCEPTION, "參數(shù)體不能為空");
}
/**
* 兼容Validation校驗框架:參數(shù)效驗異常處理器
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResult parameterExceptionHandler(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
// 獲取異常信息
BindingResult exceptions = e.getBindingResult();
// 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認消息
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
// 這里列出了全部錯誤參數(shù),按正常邏輯,只需要第一條錯誤即可
FieldError fieldError = (FieldError) errors.get(0);
return ErrorResult.error(PARAMETER_EXCEPTION, fieldError.getDefaultMessage());
}
}
return ErrorResult.error(PARAMETER_EXCEPTION, "請求參數(shù)校驗異常");
}
}
接口調(diào)用
@ResponseResult
@GetMapping
public User update() {
// 非自定義的運行時異常
long id = 10 / 0;
return userService.queryById(id);
}
@ResponseResult
@PostMapping
public User insert() {
// 拋出自定義的基礎異常
throw new BaseException();
}
@ResponseResult
@DeleteMapping
public boolean delete() {
// 拋出自定義的業(yè)務異常
throw new BizException(USER_NOT_EXIST_ERROR);
}
測試結果



總結
到此這篇關于SpringBoot中如何統(tǒng)一接口返回與全局異常處理的文章就介紹到這了,更多相關SpringBoot統(tǒng)一接口返回內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot this調(diào)用@Bean效果詳解
這篇文章主要介紹了在一個@Bean方法內(nèi),this調(diào)用同一個類的@Bean方法會有什么效果,我們可以通過bean的名稱、bean的類型或者bean的名稱+類型來獲取容器中的bean2023-02-02
Maven根據(jù)不同環(huán)境打包不同配置文件的方法
這篇文章主要介紹了Maven根據(jù)不同環(huán)境打包不同配置文件的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
springboot整合spring-data-redis遇到的坑
使用springboot整合redis,使用默認的序列化配置,然后使用redis-client去查詢時查詢不到相應的key.問題出在哪,怎么解決呢?下面小編給大家?guī)砹藄pringboot整合spring-data-redis遇到的坑,需要的的朋友參考下吧2017-04-04
intellij idea 啟動tomcat 1099端口被占用的解決
這篇文章主要介紹了intellij idea 啟動tomcat 1099端口被占用的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09

