@RestControllerAdvice注解
在做前后端分离的项目时,后端业务通常会使用多个微服务,我们希望在每一个微服务的调用接口返回给前端的结果都是统一的数据结构,如:
{
"successful": true,
"code": "200",
"message": "success",
"data": "this is data"
}
在上面的结构中,有请求是否成功标识-successful,其值为boolean类型;有服务处理结果编码-code,其值为String,可以封装自定义编码,也可以使用HttpStatus;有服务处理结果文本信息message;还有业务返回数据-data,其值类型为Object,即可以返回String、List、对象信息等等。
定义统一的返回结构体:
/**
* 返回结构体
* @author Yoko
*/
@Data
@Builder
public class ResponseDto implements Serializable {
private static final long serialVersionUID = 2684402708467978694L;
private Boolean successful;
private String code;
private String message;
private Object data;
public static ResponseDto success() {
return ResponseDto.builder().successful(true).code(String.valueOf(HttpStatus.OK.value())).message(HttpStatus.OK.getReasonPhrase()).build();
}
public static ResponseDto success(Object data) {
ResponseDto responseDto = success();
responseDto.setData(data);
return responseDto;
}
public static ResponseDto fail() {
return ResponseDto.builder().successful(false).code(ExceptionEnum.SERVER_ERROR.getCode()).message(ExceptionEnum.SERVER_ERROR.getMessage()).build();
}
public static ResponseDto fail(String message) {
ResponseDto responseDto = fail();
responseDto.setMessage(message);
return responseDto;
}
public static ResponseDto fail(String code,String message) {
ResponseDto responseDto = fail();
responseDto.setCode(code);
responseDto.setMessage(message);
return responseDto;
}
public static ResponseDto fail(String code,String message, Object data) {
ResponseDto responseDto = fail();
responseDto.setCode(code);
responseDto.setMessage(message);
responseDto.setData(data);
return responseDto;
}
}
对于正常的返回场景,我们可以直接进行封装数据,而对于异常场景,也希望使用上面的统一数据格式,而如果在controller层使用try…catch来处理异常,那么会产生大量的try…catch,代码会变的很乱。这种场景可以使用Spring Boot的@RestControllerAdvice注解来实现封装异常信息返回给前端。
1-1.定义异常枚举:
/**
* @author Yoko
*/
public enum ExceptionEnum {
/**
* code: 异常编码
* message:异常信息
*/
BAD_REQUEST("400", "请求数据格式非法。"),
FORBIDDEN("403", "没有访问权限!"),
NO_RESOURCE("404", "请求的资源找不到!"),
SERVER_ERROR("500", "服务器内部错误!"),
SERVICE_BUSY("503", "服务器正忙,请稍后再试!"),
UNKNOWN("10000", "未知异常!"),
IS_NOT_NULL("10001","%s不能为空");
private String code;
private String message;
ExceptionEnum(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
1-2.自定义异常类:
/**
* @author Yoko
*/
public class BusinessException extends RuntimeException{
private static final long serialVersionUID = -330737451734079004L;
private ExceptionEnum exceptionEnum;
private String code;
private String message;
public BusinessException() {
super();
}
public BusinessException(ExceptionEnum exceptionEnum) {
this.exceptionEnum = exceptionEnum;
this.code = exceptionEnum.getCode();
this.message = exceptionEnum.getMessage();
}
public BusinessException(String code, String message) {
this.code = code;
this.message = message;
}
public BusinessException(String code, String message, Object... args) {
this.code = code;
this.message = String.format(message, args);
}
public ExceptionEnum getExceptionEnum() {
return exceptionEnum;
}
public void setExceptionEnum(ExceptionEnum exceptionEnum) {
this.exceptionEnum = exceptionEnum;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2.定义RestControllerAdvice捕获全局异常: @ResponseBody 是不是可以去掉,在恒星项目中没有使用
/**
* @author Yoko
* @date 2021/11/19
*/
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class ExceptionHandlerConfig {
/**
* 业务异常处理
*/
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ResponseDto exceptionHandler(BusinessException e) {
return ResponseDto.fail(e.getCode(), e.getMessage());
}
/**
* 未知异常处理
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseDto exceptionHandler(Exception e) {
return ResponseDto.fail(ExceptionEnum.UNKNOWN.getCode(),
ExceptionEnum.UNKNOWN.getMessage());
}
/**
* 空指针异常
*/
@ExceptionHandler(value = NullPointerException.class)
@ResponseBody
public ResponseDto exceptionHandler(NullPointerException e) {
return ResponseDto.fail(ExceptionEnum.SERVER_ERROR.getCode(),
ExceptionEnum.SERVER_ERROR.getMessage());
}
}
3-1.定义service:
/**
* @author Yoko
*/
@Service
public class TestServiceImpl {
public Object test1() {
return "hello world";
}
public Object test3() {
throw new BusinessException(ExceptionEnum.NO_RESOURCE.getCode(), ExceptionEnum.NO_RESOURCE.getMessage());
}
}
3-2.定义controller:
/**
* @author Yoko
*/
@Slf4j
@RestController
public class RestControllerAdviceTest {
@Resource
private TestServiceImpl testService;
@RequestMapping(name = "正常场景", value = "/test1", method = RequestMethod.PUT)
public ResponseDto test1() {
return ResponseDto.success(this.testService.test1());
}
@RequestMapping(name = "空指针场景", value = "/test2", method = RequestMethod.PUT)
public ResponseDto test2() {
String str = null;
str.equals("abc");
return ResponseDto.success();
}
@RequestMapping(name = "指定错误异常", value = "/test3", method = RequestMethod.PUT)
public ResponseDto test3() {
this.testService.test3();
return ResponseDto.success(this.testService.test1());
}
}
测试结果:
注:ControllerAdvice和RestControllerAdvice的区别:
两者都是全局捕获异常,但是RestControllerAdvice更加强大,其作用相当于ControllerAdvice+ResponseBody