SpringBoot如何統(tǒng)一處理返回結(jié)果和異常情況
原因
在springboot項目里我們希望接口返回的數(shù)據(jù)包含至少三個屬性:
- code:請求接口的返回碼,成功或者異常等返回編碼,例如定義請求成功。
- message:請求接口的描述,也就是對返回編碼的描述。
- data:請求接口成功,返回的結(jié)果。
{ "code":20000, "message":"成功", "data":{ "info":"測試成功" } }
開發(fā)環(huán)境
- 工具:IDEA
- SpringBoot版本:2.2.2.RELEASE
依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <!-- Spring web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- swagger3 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency>
創(chuàng)建 SpringBoot 工程#
新建 SpringBoot 項目common_utils,包名com.spring.utils
返回結(jié)果統(tǒng)一
創(chuàng)建code枚舉
在com.spring.utils中創(chuàng)建 pojo 包,并添加枚舉ResultCode
public enum ResultCode { /* 成功狀態(tài)碼 */ SUCCESS(20000, "成功"), /* 參數(shù)錯誤 */ PARAM_IS_INVALID(1001, "參數(shù)無效"), PARAM_IS_BLANK(1002, "參數(shù)為空"), PARAM_TYPE_BIND_ERROR(1003, "參數(shù)類型錯誤"), PARAM_NOT_COMPLETE(1004, "參數(shù)缺失"), /* 用戶錯誤 2001-2999*/ USER_NOTLOGGED_IN(2001, "用戶未登錄"), USER_LOGIN_ERROR(2002, "賬號不存在或密碼錯誤"), SYSTEM_ERROR(10000, "系統(tǒng)異常,請稍后重試"); private Integer code; private String message; private ResultCode(Integer code, String message) { this.code = code; this.message = message; } public Integer code() { return this.code; } public String message() { return this.message; } }
**可根據(jù)項目自定義,結(jié)果返回碼
創(chuàng)建返回結(jié)果實體
在 pojo 包中添加返回結(jié)果類R**
@Data @ApiModel(value = "返回結(jié)果實體類", description = "結(jié)果實體類") public class R implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "返回碼") private Integer code; @ApiModelProperty(value = "返回消息") private String message; @ApiModelProperty(value = "返回數(shù)據(jù)") private Object data; private R() { } public R(ResultCode resultCode, Object data) { this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; } private void setResultCode(ResultCode resultCode) { this.code = resultCode.code(); this.message = resultCode.message(); } // 返回成功 public static R success() { R result = new R(); result.setResultCode(ResultCode.SUCCESS); return result; } // 返回成功 public static R success(Object data) { R result = new R(); result.setResultCode(ResultCode.SUCCESS); result.setData(data); return result; } // 返回失敗 public static R fail(Integer code, String message) { R result = new R(); result.setCode(code); result.setMessage(message); return result; } // 返回失敗 public static R fail(ResultCode resultCode) { R result = new R(); result.setResultCode(resultCode); return result; } }
自定義一個注解
新建包annotation,并添加ResponseResult注解類
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD}) @Documented public @interface ResponseResult { }
定義攔截器
新建包interceptor,并添加ResponseResultInterceptorJava類
@Component public class ResponseResultInterceptor implements HandlerInterceptor { //標記名稱 public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // TODO Auto-generated method stub 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_ANN, clazz.getAnnotation(ResponseResult.class)); } else if (method.isAnnotationPresent(ResponseResult.class)) { request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class)); } } return true; } }
用于攔截請求,判斷 Controller 是否添加了@ResponseResult注解
注冊攔截器
新建包config,并添加WebAppConfig配置類
@Configuration public class WebAppConfig implements WebMvcConfigurer { // SpringMVC 需要手動添加攔截器 @Override public void addInterceptors(InterceptorRegistry registry) { ResponseResultInterceptor interceptor = new ResponseResultInterceptor(); registry.addInterceptor(interceptor); WebMvcConfigurer.super.addInterceptors(registry); } }
方法返回值攔截處理器
新建包handler,并添加ResponseResultHandler配置類,實現(xiàn)ResponseBodyAdvice重寫兩個方法
import org.springframework.web.method.HandlerMethod; /** * 使用 @ControllerAdvice & ResponseBodyAdvice * 攔截Controller方法默認返回參數(shù),統(tǒng)一處理返回值/響應體 */ @ControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { // 標記名稱 public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; // 判斷是否要執(zhí)行 beforeBodyWrite 方法,true為執(zhí)行,false不執(zhí)行,有注解標記的時候處理返回值 @Override public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) { ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = sra.getRequest(); // 判斷請求是否有包裝標記 ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); return responseResultAnn == null ? false : true; } // 對返回值做包裝處理,如果屬于異常結(jié)果,則需要再包裝 @Override public Object beforeBodyWrite(Object body, MethodParameter arg1, MediaType arg2, Class<? extends HttpMessageConverter<?>> arg3, ServerHttpRequest arg4, ServerHttpResponse arg5) { if (body instanceof R) { return (R) body; } return R.success(body); } }
實現(xiàn)ResponseBodyAdvice重寫兩個方法
添加@ControllerAdvice注解
測試#
新建包controller,并添加TestController測試類
@RestController @ResponseResult public class TestController { @GetMapping("/test") public Map<String, Object> test() { HashMap<String, Object> data = new HashMap<>(); data.put("info", "測試成功"); return data; } }
添加@ResponseResult注解
啟動項目,在默認端口: 8080
瀏覽器訪問地址:localhost:8080/test
{"code":20000,"message":"成功","data":{"info":"測試成功"}}
總結(jié)
1、創(chuàng)建code枚舉和返回結(jié)果實體類
2、自定義一個注解@ResponseResult
3、定義攔截器,攔截請求,判斷Controller上是否添加了@ResponseResult注解。如果添加了注解在request中添加注解標記,往下傳遞
4、添加@ControllerAdvice注解 ,實現(xiàn)ResponseBodyAdvice接口,并重寫兩個方法,通過判斷request中是否有注解標記,如果有就往下執(zhí)行,進一步包裝。沒有就直接返回,不需包裝。
問題
1、如果要返回錯誤結(jié)果,這種方法顯然不方便
@GetMapping("/fail") public R error() { int res = 0; // 查詢結(jié)果數(shù) if( res == 0 ) { return R.fail(10001, "沒有數(shù)據(jù)"); } return R.success(res); }
2、我們需要對錯誤和異常進行進一步的封裝
封裝錯誤和異常結(jié)果#
創(chuàng)建錯誤結(jié)果實體#
在包pojo中添加ErrorResult實體類
/** * 異常結(jié)果包裝類 * @author sw-code * */ public class ErrorResult { private Integer code; private String message; private String exception; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getException() { return exception; } public void setException(String exception) { this.exception = exception; } public static ErrorResult fail(ResultCode resultCode, Throwable e, String message) { ErrorResult errorResult = ErrorResult.fail(resultCode, e); errorResult.setMessage(message); return errorResult; } public static ErrorResult fail(ResultCode resultCode, Throwable e) { ErrorResult errorResult = new ErrorResult(); errorResult.setCode(resultCode.code()); errorResult.setMessage(resultCode.message()); errorResult.setException(e.getClass().getName()); return errorResult; } public static ErrorResult fail(Integer code, String message) { ErrorResult errorResult = new ErrorResult(); errorResult.setCode(code); errorResult.setMessage(message); return errorResult; } }
自定義異常類
在包pojo中添加BizException實體類,繼承RuntimeException
@Data public class BizException extends RuntimeException { /** * 錯誤碼 */ private Integer code; /** * 錯誤信息 */ private String message; public BizException() { super(); } public BizException(ResultCode resultCode) { super(resultCode.message()); this.code = resultCode.code(); this.message = resultCode.message(); } public BizException(ResultCode resultCode, Throwable cause) { super(resultCode.message(), cause); this.code = resultCode.code(); this.message = resultCode.message(); } public BizException(String message) { super(message); this.setCode(-1); this.message = message; } public BizException(Integer code, String message) { super(message); this.code = code; this.message = message; } public BizException(Integer code, String message, Throwable cause) { super(message, cause); this.code = code; this.message = message; } @Override public synchronized Throwable fillInStackTrace() { return this; } }
全局異常處理類
在包handler中添加GlobalExceptionHandler,添加@RestControllerAdvice注解
/** * 全局異常處理類 * @RestControllerAdvice(@ControllerAdvice),攔截異常并統(tǒng)一處理 * @author sw-code * */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 處理自定義的業(yè)務異常 * @param e 異常對象 * @param request request * @return 錯誤結(jié)果 */ @ExceptionHandler(BizException.class) public ErrorResult bizExceptionHandler(BizException e, HttpServletRequest request) { log.error("發(fā)生業(yè)務異常!原因是: {}", e.getMessage()); return ErrorResult.fail(e.getCode(), e.getMessage()); } // 攔截拋出的異常,@ResponseStatus:用來改變響應狀態(tài)碼 @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Throwable.class) public ErrorResult handlerThrowable(Throwable e, HttpServletRequest request) { log.error("發(fā)生未知異常!原因是: ", e); ErrorResult error = ErrorResult.fail(ResultCode.SYSTEM_ERROR, e); return error; } // 參數(shù)校驗異常 @ExceptionHandler(BindException.class) public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) { log.error("發(fā)生參數(shù)校驗異常!原因是:",e); ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID, e, e.getAllErrors().get(0).getDefaultMessage()); return error; } @ExceptionHandler(MethodArgumentNotValidException.class) public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { log.error("發(fā)生參數(shù)校驗異常!原因是:",e); ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID,e,e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return error; } }
添加注解@RestControllerAdvice(@ControllerAdvice),攔截異常并統(tǒng)一處理
修改方法返回值攔截處理器#
將錯誤和異常結(jié)果也進行統(tǒng)一封裝
/** * 使用 @ControllerAdvice & ResponseBodyAdvice * 攔截Controller方法默認返回參數(shù),統(tǒng)一處理返回值/響應體 */ @ControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { // 標記名稱 public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; // 判斷是否要執(zhí)行 beforeBodyWrite 方法,true為執(zhí)行,false不執(zhí)行,有注解標記的時候處理返回值 @Override public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) { ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = sra.getRequest(); // 判斷請求是否有包裝標記 ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); return responseResultAnn == null ? false : true; } // 對返回值做包裝處理,如果屬于異常結(jié)果,則需要再包裝 @Override public Object beforeBodyWrite(Object body, MethodParameter arg1, MediaType arg2, Class<? extends HttpMessageConverter<?>> arg3, ServerHttpRequest arg4, ServerHttpResponse arg5) { if (body instanceof ErrorResult) { ErrorResult error = (ErrorResult) body; return R.fail(error.getCode(), error.getMessage()); } else if (body instanceof R) { return (R) body; } return R.success(body); } } 測試# Copy @GetMapping("/fail") public Integer error() { int res = 0; // 查詢結(jié)果數(shù) if( res == 0 ) { throw new BizException("沒有數(shù)據(jù)"); } return res; }
返回結(jié)果
{"code":-1,"message":"沒有數(shù)據(jù)","data":null}
我們無需擔心返回類型,如果需要返回錯誤提示信息,可以直接拋出自定義異常(BizException),并添加自定義錯誤信息。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- SpringBoot統(tǒng)一數(shù)據(jù)返回格式的實現(xiàn)示例
- 詳解SpringBoot中的統(tǒng)一結(jié)果返回與統(tǒng)一異常處理
- Springboot設置統(tǒng)一的返回格式的方法步驟
- SpringBoot返回結(jié)果統(tǒng)一處理實例詳解
- 淺析SpringBoot統(tǒng)一返回結(jié)果的實現(xiàn)
- SpringBoot全局處理統(tǒng)一返回類型方式
- SpringBoot統(tǒng)一返回結(jié)果問題
- 詳解SpringBoot如何統(tǒng)一處理返回的信息
- SpringBoot統(tǒng)一返回格式的方法詳解
- SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式
相關文章
MybatisPlus BaseMapper 中的方法全部 Invalid bound statement (not f
這篇文章主要介紹了MybatisPlus BaseMapper 中的方法全部 Invalid bound statement (not found)的Error處理方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09Java設計模式之狀態(tài)模式State Pattern詳解
這篇文章主要介紹了Java設計模式之狀態(tài)模式State Pattern,狀態(tài)模式允許一個對象在其內(nèi)部狀態(tài)改變的時候改變其行為。這個對象看上去就像是改變了它的類一樣2022-11-11SpringCloud2020版本配置與環(huán)境搭建教程詳解
這篇文章主要介紹了SpringCloud2020版本配置與環(huán)境搭建教程詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12SpringBoot?Security權(quán)限控制自定義failureHandler實例
這篇文章主要為大家介紹了SpringBoot?Security權(quán)限控制自定義failureHandler實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11