在做前后端分离的项目时,后端业务通常会使用多个微服务,我们希望在每一个微服务的调用接口返回给前端的结果都是统一的数据结构,如:
{
"successful": true,
"code": "200",
"message": "success",
"data": "this is data"
}
123456
在上面的结构中,有请求是否成功标识-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;
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
对于正常的返回场景,我们可以直接进行封装数据,而对于异常场景,也希望使用上面的统一数据格式,而如果在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;
}
}
1234567891011121314151617181920212223242526272829
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;
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445
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());
}
}
12345678910111213141516171819202122232425262728293031323334353637
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());
}
}
1234567891011121314
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());
}
}
1234567891011121314151617181920212223242526
测试结果:
注:ControllerAdvice和RestControllerAdvice的区别:
两者都是全局捕获异常,但是RestControllerAdvice更加强大,其作用相当于ControllerAdvice+ResponseBody
@RestControllerAdvice的特点:
通过@ControllerAdvice注解可以将对于控制器的全局配置放在同一个位置。
注解了@RestControllerAdvice的类的方法可以使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上。
@RestControllerAdvice注解将作用在所有注解了@RequestMapping的控制器的方法上。
@ExceptionHandler:用于指定异常处理方法。当与@RestControllerAdvice配合使用时,用于全局处理控制器里的异常。
@InitBinder:用来设置WebDataBinder,用于自动绑定前台请求参数到Model中。
@ModelAttribute:本来作用是绑定键值对到Model中,当与@ControllerAdvice配合使用时,可以让全局的@RequestMapping都能获得在此处设置的键值对
@ControllerAdvicepublicclassGlobalController{
//(1)全局数据绑定//应用到所有@RequestMapping注解方法 //此处将键值对添加到全局,注解了@RequestMapping的方法都可以获得此键值对 @ModelAttributepublicvoidaddUser(Model model) {
model.addAttribute("msg", "此处将键值对添加到全局,注解了@RequestMapping的方法都可以获得此键值对");
}
//(2)全局数据预处理//应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器 //用来设置WebDataBinder @InitBinder("user")
publicvoidinitBinder(WebDataBinder binder) {
}
// (3)全局异常处理//应用到所有@RequestMapping注解的方法,在其抛出Exception异常时执行 //定义全局异常处理,value属性可以过滤拦截指定异常,此处拦截所有的Exception @ExceptionHandler(Exception.class)
publicStringhandleException(Exception e) {
return"error";
}
}
@ControllerAdvice可以指定 Controller 范围
basePackages: 指定一个或多个包,这些包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
@RestControllerAdvice(basePackages={"top.onething"})@Slf4jpublicclassExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)public String handleException(Exception e) {
return"error";
}
}
basePackageClasses: 是 basePackages 的一种变形,指定一个或多个 Controller 类,这些类所属的包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
@RestControllerAdvice(basePackageClasses={TestController.class})@Slf4jpublicclassExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)public String handleException(Exception e) {
return"error";
}
}
assignableTypes: 指定一个或多个 Controller 类,这些类被该 @ControllerAdvice 管理
@RestControllerAdvice(assignableTypes={TestController.class})@Slf4jpublicclassExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)public String handleException(Exception e) {
return"error";
}
}
annotations: 指定一个或多个注解,被这些注解所标记的 Controller 会被该 @ControllerAdvice 管理
@ControllerAdvice(annotations = {TestAnnotation.class})@Slf4jpublicclassExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)public String handleException(Exception e) {
return"error";
}
}