SpringBoot如何統(tǒng)一處理返回結(jié)果和異常情況
原因
在springboot項(xiàng)目里我們希望接口返回的數(shù)據(jù)包含至少三個(gè)屬性:
- code:請(qǐng)求接口的返回碼,成功或者異常等返回編碼,例如定義請(qǐng)求成功。
- message:請(qǐng)求接口的描述,也就是對(duì)返回編碼的描述。
- data:請(qǐng)求接口成功,返回的結(jié)果。
{
"code":20000,
"message":"成功",
"data":{
"info":"測(cè)試成功"
}
}開(kāi)發(fā)環(huán)境
- 工具:IDEA
- SpringBoot版本:2.2.2.RELEASE
依賴(lài)
<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 項(xiàng)目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ù)錯(cuò)誤 */
PARAM_IS_INVALID(1001, "參數(shù)無(wú)效"),
PARAM_IS_BLANK(1002, "參數(shù)為空"),
PARAM_TYPE_BIND_ERROR(1003, "參數(shù)類(lèi)型錯(cuò)誤"),
PARAM_NOT_COMPLETE(1004, "參數(shù)缺失"),
/* 用戶(hù)錯(cuò)誤 2001-2999*/
USER_NOTLOGGED_IN(2001, "用戶(hù)未登錄"),
USER_LOGIN_ERROR(2002, "賬號(hào)不存在或密碼錯(cuò)誤"),
SYSTEM_ERROR(10000, "系統(tǒng)異常,請(qǐ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ù)項(xiàng)目自定義,結(jié)果返回碼
創(chuàng)建返回結(jié)果實(shí)體
在 pojo 包中添加返回結(jié)果類(lèi)R**
@Data
@ApiModel(value = "返回結(jié)果實(shí)體類(lèi)", description = "結(jié)果實(shí)體類(lèi)")
public class R implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "返回碼")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回?cái)?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;
}
}自定義一個(gè)注解
新建包annotation,并添加ResponseResult注解類(lèi)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface ResponseResult {
}定義攔截器
新建包interceptor,并添加ResponseResultInterceptorJava類(lèi)
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
//標(biāo)記名稱(chēng)
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();
// 判斷是否在類(lèi)對(duì)象上添加了注解
if (clazz.isAnnotationPresent(ResponseResult.class)) {
// 設(shè)置此請(qǐng)求返回體,需要包裝,往下傳遞,在ResponseBodyAdvice接口進(jìn)行判斷
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;
}
}用于攔截請(qǐng)求,判斷 Controller 是否添加了@ResponseResult注解
注冊(cè)攔截器
新建包c(diǎn)onfig,并添加WebAppConfig配置類(lèi)
@Configuration
public class WebAppConfig implements WebMvcConfigurer {
// SpringMVC 需要手動(dòng)添加攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
ResponseResultInterceptor interceptor = new ResponseResultInterceptor();
registry.addInterceptor(interceptor);
WebMvcConfigurer.super.addInterceptors(registry);
}
}方法返回值攔截處理器
新建包handler,并添加ResponseResultHandler配置類(lèi),實(shí)現(xiàn)ResponseBodyAdvice重寫(xiě)兩個(gè)方法
import org.springframework.web.method.HandlerMethod;
/**
* 使用 @ControllerAdvice & ResponseBodyAdvice
* 攔截Controller方法默認(rèn)返回參數(shù),統(tǒng)一處理返回值/響應(yīng)體
*/
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
// 標(biāo)記名稱(chēng)
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
// 判斷是否要執(zhí)行 beforeBodyWrite 方法,true為執(zhí)行,false不執(zhí)行,有注解標(biāo)記的時(shí)候處理返回值
@Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
// 判斷請(qǐng)求是否有包裝標(biāo)記
ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
return responseResultAnn == null ? false : true;
}
// 對(duì)返回值做包裝處理,如果屬于異常結(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);
}
}實(shí)現(xiàn)ResponseBodyAdvice重寫(xiě)兩個(gè)方法
添加@ControllerAdvice注解
測(cè)試#
新建包c(diǎn)ontroller,并添加TestController測(cè)試類(lèi)
@RestController
@ResponseResult
public class TestController {
@GetMapping("/test")
public Map<String, Object> test() {
HashMap<String, Object> data = new HashMap<>();
data.put("info", "測(cè)試成功");
return data;
}
}添加@ResponseResult注解
啟動(dòng)項(xiàng)目,在默認(rèn)端口: 8080
瀏覽器訪問(wèn)地址:localhost:8080/test
{"code":20000,"message":"成功","data":{"info":"測(cè)試成功"}}總結(jié)
1、創(chuàng)建code枚舉和返回結(jié)果實(shí)體類(lèi)
2、自定義一個(gè)注解@ResponseResult
3、定義攔截器,攔截請(qǐng)求,判斷Controller上是否添加了@ResponseResult注解。如果添加了注解在request中添加注解標(biāo)記,往下傳遞
4、添加@ControllerAdvice注解 ,實(shí)現(xiàn)ResponseBodyAdvice接口,并重寫(xiě)兩個(gè)方法,通過(guò)判斷request中是否有注解標(biāo)記,如果有就往下執(zhí)行,進(jìn)一步包裝。沒(méi)有就直接返回,不需包裝。
問(wèn)題
1、如果要返回錯(cuò)誤結(jié)果,這種方法顯然不方便
@GetMapping("/fail")
public R error() {
int res = 0; // 查詢(xún)結(jié)果數(shù)
if( res == 0 ) {
return R.fail(10001, "沒(méi)有數(shù)據(jù)");
}
return R.success(res);
}2、我們需要對(duì)錯(cuò)誤和異常進(jìn)行進(jìn)一步的封裝
封裝錯(cuò)誤和異常結(jié)果#
創(chuàng)建錯(cuò)誤結(jié)果實(shí)體#
在包pojo中添加ErrorResult實(shí)體類(lèi)
/**
* 異常結(jié)果包裝類(lèi)
* @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;
}
}自定義異常類(lèi)
在包pojo中添加BizException實(shí)體類(lèi),繼承RuntimeException
@Data
public class BizException extends RuntimeException {
/**
* 錯(cuò)誤碼
*/
private Integer code;
/**
* 錯(cuò)誤信息
*/
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;
}
}全局異常處理類(lèi)
在包handler中添加GlobalExceptionHandler,添加@RestControllerAdvice注解
/**
* 全局異常處理類(lèi)
* @RestControllerAdvice(@ControllerAdvice),攔截異常并統(tǒng)一處理
* @author sw-code
*
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理自定義的業(yè)務(wù)異常
* @param e 異常對(duì)象
* @param request request
* @return 錯(cuò)誤結(jié)果
*/
@ExceptionHandler(BizException.class)
public ErrorResult bizExceptionHandler(BizException e, HttpServletRequest request) {
log.error("發(fā)生業(yè)務(wù)異常!原因是: {}", e.getMessage());
return ErrorResult.fail(e.getCode(), e.getMessage());
}
// 攔截拋出的異常,@ResponseStatus:用來(lái)改變響應(yīng)狀態(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ù)校驗(yàn)異常
@ExceptionHandler(BindException.class)
public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) {
log.error("發(fā)生參數(shù)校驗(yàn)異常!原因是:",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ù)校驗(yàn)異常!原因是:",e);
ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID,e,e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return error;
}
}添加注解@RestControllerAdvice(@ControllerAdvice),攔截異常并統(tǒng)一處理
修改方法返回值攔截處理器#
將錯(cuò)誤和異常結(jié)果也進(jìn)行統(tǒng)一封裝
/**
* 使用 @ControllerAdvice & ResponseBodyAdvice
* 攔截Controller方法默認(rèn)返回參數(shù),統(tǒng)一處理返回值/響應(yīng)體
*/
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
// 標(biāo)記名稱(chēng)
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
// 判斷是否要執(zhí)行 beforeBodyWrite 方法,true為執(zhí)行,false不執(zhí)行,有注解標(biāo)記的時(shí)候處理返回值
@Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
// 判斷請(qǐng)求是否有包裝標(biāo)記
ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
return responseResultAnn == null ? false : true;
}
// 對(duì)返回值做包裝處理,如果屬于異常結(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);
}
}
測(cè)試#
Copy
@GetMapping("/fail")
public Integer error() {
int res = 0; // 查詢(xún)結(jié)果數(shù)
if( res == 0 ) {
throw new BizException("沒(méi)有數(shù)據(jù)");
}
return res;
}返回結(jié)果
{"code":-1,"message":"沒(méi)有數(shù)據(jù)","data":null}
我們無(wú)需擔(dān)心返回類(lèi)型,如果需要返回錯(cuò)誤提示信息,可以直接拋出自定義異常(BizException),并添加自定義錯(cuò)誤信息。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot統(tǒng)一數(shù)據(jù)返回格式的實(shí)現(xiàn)示例
- 詳解SpringBoot中的統(tǒng)一結(jié)果返回與統(tǒng)一異常處理
- Springboot設(shè)置統(tǒng)一的返回格式的方法步驟
- SpringBoot返回結(jié)果統(tǒng)一處理實(shí)例詳解
- 淺析SpringBoot統(tǒng)一返回結(jié)果的實(shí)現(xiàn)
- SpringBoot全局處理統(tǒng)一返回類(lèi)型方式
- SpringBoot統(tǒng)一返回結(jié)果問(wèn)題
- 詳解SpringBoot如何統(tǒng)一處理返回的信息
- SpringBoot統(tǒng)一返回格式的方法詳解
- SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式
相關(guān)文章
解決spring mvc 多數(shù)據(jù)源切換,不支持事務(wù)控制的問(wèn)題
下面小編就為大家?guī)?lái)一篇解決spring mvc 多數(shù)據(jù)源切換,不支持事務(wù)控制的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
Java Feign微服務(wù)接口調(diào)用方法詳細(xì)講解
現(xiàn)如今微服務(wù)架構(gòu)十分流行,而采用微服務(wù)構(gòu)建系統(tǒng)也會(huì)帶來(lái)更清晰的業(yè)務(wù)劃分和可擴(kuò)展性。java如果使用微服務(wù)就離不開(kāi)springcloud,我這里是把服務(wù)注冊(cè)到nacos上,各個(gè)服務(wù)之間的調(diào)用使用feign2023-01-01
Spring-Cloud Eureka注冊(cè)中心實(shí)現(xiàn)高可用搭建
這篇文章主要介紹了Spring-Cloud Eureka注冊(cè)中心實(shí)現(xiàn)高可用搭建,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Java String字符串補(bǔ)0或空格的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java String字符串補(bǔ)0或空格的實(shí)現(xiàn)代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-09-09
SpringBoot Redis實(shí)現(xiàn)接口冪等性校驗(yàn)方法詳細(xì)講解
這篇文章主要介紹了SpringBoot Redis實(shí)現(xiàn)接口冪等性校驗(yàn)方法,近期一個(gè)老項(xiàng)目出現(xiàn)了接口冪等性校驗(yàn)問(wèn)題,前端加了按鈕置灰,依然被人拉著接口參數(shù)一頓輸出,還是重復(fù)調(diào)用了接口,通過(guò)復(fù)制粘貼,完成了后端接口冪等性調(diào)用校驗(yàn)2022-11-11

