背景
在分布式、微服務(wù)盛行的今天,絕大部分項(xiàng)目都采用的微服務(wù)框架,前后端分離方式。前端和后端進(jìn)行交互,前端按照約定請求URL路徑,并傳入相關(guān)參數(shù),后端服務(wù)器接收請求,進(jìn)行業(yè)務(wù)處理,返回?cái)?shù)據(jù)給前端。維護(hù)一套完善且規(guī)范的接口是非常有必要的, 這樣不僅能夠提高對(duì)接效率,也可以讓我的代碼看起來更加簡潔優(yōu)雅。
使用統(tǒng)一返回結(jié)果時(shí),還有一種情況,就是程序的報(bào)錯(cuò)是由于運(yùn)行時(shí)異常導(dǎo)致的結(jié)果,有些異常是我們在業(yè)務(wù)中拋出的,有些是無法提前預(yù)知。
因此,我們需要定義一個(gè)統(tǒng)一的全局異常,在Controller捕獲所有異常,并且做適當(dāng)處理,并作為一種結(jié)果返回。
統(tǒng)一接口返回
定義API返回碼枚舉類
public enum ResultCode { /* 成功狀態(tài)碼 */ SUCCESS(200, "成功"), /* 錯(cuò)誤狀態(tài)碼 */ NOT_FOUND(404, "請求的資源不存在"), INTERNAL_ERROR(500, "服務(wù)器內(nèi)部錯(cuò)誤"), PARAMETER_EXCEPTION(501, "請求參數(shù)校驗(yàn)異常"), /* 業(yè)務(wù)狀態(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; } }
定義正常響應(yīng)的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; } }
定義異常響應(yīng)的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; } }
編寫包裝返回結(jié)果的自定義注解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) //作用于方法和類(接口)上 @Documented public @interface ResponseResult { }
定義返回結(jié)果攔截器
@Component public class ResponseResultInterceptor implements HandlerInterceptor { /* 使用統(tǒng)一返回體的標(biāo)識(shí) */ 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; // 獲取當(dāng)前類 final Class<?> clazz = handlerMethod.getBeanType(); // 獲取當(dāng)前方法 final Method method = handlerMethod.getMethod(); // 判斷是否在類對(duì)象上加了注解 if (clazz.isAnnotationPresent(ResponseResult.class)) { // 設(shè)置該請求返回體,需要包裝,往下傳遞,在ResponseBodyAdvice接口進(jìn)行判斷 request.setAttribute(RESPONSE_RESULT_ANNOTATION, clazz.getAnnotation(ResponseResult.class)); } // 判斷是否在方法上加了注解 else if (method.isAnnotationPresent(ResponseResult.class)) { // 設(shè)置該請求返回體,需要包裝,往下傳遞,在ResponseBodyAdvice接口進(jìn)行判斷 request.setAttribute(RESPONSE_RESULT_ANNOTATION, method.getAnnotation(ResponseResult.class)); } } return true; } }
WebMvc配置類攔截器注冊者添加返回結(jié)果攔截器
@Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * 添加自定義攔截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/**"); } }
編寫響應(yīng)體處理器
/** * 統(tǒng)一處理響應(yīng)體,用Result.success靜態(tài)方法包裝, * 在API接口使用時(shí)就可以直接返回原始類型 */ @RestControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { /* 使用統(tǒng)一返回體的標(biāo)識(shí) */ 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) { // 異常響應(yīng)體則直接返回code+message的消息體 if (body instanceof ErrorResult) { return body; } // 正常響應(yīng)體則返回Result包裝的code+message+data的消息體 return Result.success(body); } }
接口調(diào)用
@Api("用戶管理") @RestController @RequestMapping("user") @ResponseResult // 作用于類上,對(duì)所有接口有效 public class UserController { @Autowired private UserService userService; @ResponseResult // 作用于方法上 @ApiOperation("根據(jù)ID查詢用戶") @GetMapping("one") public User selectOne(Long id) { // 由于在ResponseResultHandler中已經(jīng)統(tǒng)一將返回?cái)?shù)據(jù)用Result.success包裝了, // 直接返回原始類型即可,代碼更簡潔 return this.userService.queryById(id); } @ResponseResult @ApiOperation("查詢所有用戶") @GetMapping("all") public List<User> selectAll(Page page) { // 由于在ResponseResultHandler中已經(jīng)統(tǒng)一將返回?cái)?shù)據(jù)用Result.success包裝了, // 直接返回原始類型即可,代碼更簡潔 return this.userService.queryAllByLimit(page); } }
測試結(jié)果
全局異常處理
編寫自定義異常基類
@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è)務(wù)異常類
public class BizException extends BaseException { public BizException(ResultCode resultCode) { super(resultCode); } }
定義全局異常處理類
通過@ExceptionHandler注解來統(tǒng)一處理某一類異常
@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 統(tǒng)一處理自定義基礎(chǔ)異常 */ @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è)務(wù)異常 */ @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校驗(yàn)框架:忽略參數(shù)異常處理器 */ @ExceptionHandler(MissingServletRequestParameterException.class) public ApiResult<String> parameterMissingExceptionHandler(MissingServletRequestParameterException e) { log.error(e.getMessage(), e); return ErrorResult.error(PARAMETER_EXCEPTION, "請求參數(shù) " + e.getParameterName() + " 不能為空"); } /** * 兼容Validation校驗(yàn)框架:缺少請求體異常處理器 */ @ExceptionHandler(HttpMessageNotReadableException.class) public ErrorResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) { log.error(e.getMessage(), e); return ErrorResult.error(PARAMETER_EXCEPTION, "參數(shù)體不能為空"); } /** * 兼容Validation校驗(yàn)框架:參數(shù)效驗(yàn)異常處理器 */ @ExceptionHandler(MethodArgumentNotValidException.class) public ErrorResult parameterExceptionHandler(MethodArgumentNotValidException e) { log.error(e.getMessage(), e); // 獲取異常信息 BindingResult exceptions = e.getBindingResult(); // 判斷異常中是否有錯(cuò)誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息 if (exceptions.hasErrors()) { List<ObjectError> errors = exceptions.getAllErrors(); if (!errors.isEmpty()) { // 這里列出了全部錯(cuò)誤參數(shù),按正常邏輯,只需要第一條錯(cuò)誤即可 FieldError fieldError = (FieldError) errors.get(0); return ErrorResult.error(PARAMETER_EXCEPTION, fieldError.getDefaultMessage()); } } return ErrorResult.error(PARAMETER_EXCEPTION, "請求參數(shù)校驗(yàn)異常"); } }
接口調(diào)用
@ResponseResult @GetMapping public User update() { // 非自定義的運(yùn)行時(shí)異常 long id = 10 / 0; return userService.queryById(id); } @ResponseResult @PostMapping public User insert() { // 拋出自定義的基礎(chǔ)異常 throw new BaseException(); } @ResponseResult @DeleteMapping public boolean delete() { // 拋出自定義的業(yè)務(wù)異常 throw new BizException(USER_NOT_EXIST_ERROR); }
測試結(jié)果
總結(jié)
到此這篇關(guān)于SpringBoot中如何統(tǒng)一接口返回與全局異常處理的文章就介紹到這了,更多相關(guān)SpringBoot統(tǒng)一接口返回內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://juejin.cn/post/7002926832160866318