Spring Boot 统一RESTful接口响应和统一异常处理
一、简介
基于Spring Boot 框架开发的应用程序,大部分都是以提供RESTful接口为主要的目的。前端或者移动端开发人员通过调用后端提供的接口完成数据的交换。
在一个项目中RESTful接口响应数据结构是统一的是基本的开发规范。能够减少团队内部不必要的沟通;减轻接口消费者校验数据的负担;降低其他同事接手代码的难度;提高接口的健壮性和可扩展性。
public class GlobalResponseEntity{
private Boolean success = true;
private String code = "000000";
private String message = "request successfully";
private T data;
}
统一的异常处理,是系统完备性的基本象征。通过对全局异常信息的捕获,能够避免将异常信息和系统敏感信息直接抛给客户端;针对特定类型异常捕获之后可以重新对输出数据做编排,提高交互友好度,同时可以记录异常信息以便监控和分析。
二、如何实现
运用RestControllerAdvice或者ControllerAdvice注解实现。(ControllerAdvice是RestControllerAdvice的爸爸)
@ControllerAdvice是在类上声明的注解,其用法主要有三点:
-
和@ExceptionHandler注解配合使用,@ExceptionHandler标注的方法可以捕获Controller中抛出的的异常,从而达到异常统一处理的目的
-
和@InitBinder注解配合使用,@InitBinder标注的方法可在请求中注册自定义参数的解析器,从而达到自定义请求参数格式化的目的
-
和@ModelAttribute注解配合使用,@ModelAttribute标注的方法会在执行目标Controller方法之前执行,可在入参上增加自定义信息
用法举例:
// 这里@RestControllerAdvice等同于@ControllerAdvice + @ResponseBody
@RestControllerAdvice
public class GlobalHandler {
private final Logger logger = LoggerFactory.getLogger(GlobalHandler.class);
// 这里@ModelAttribute("loginUserInfo")标注的modelAttribute()方法表示会在Controller方法之前
// 执行,返回当前登录用户的UserDetails对象
@ModelAttribute("loginUserInfo")
public UserDetails modelAttribute() {
return (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
// @InitBinder标注的initBinder()方法表示注册一个Date类型的类型转换器,用于将类似这样的2019-06-10
// 日期格式的字符串转换成Date对象
@InitBinder
protected void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// 这里表示Controller抛出的MethodArgumentNotValidException异常由这个方法处理
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result exceptionHandler(MethodArgumentNotValidException e) {
Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(),
BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg());
logger.error("req params error", e);
return result;
}
// 这里表示Controller抛出的BizException异常由这个方法处理
@ExceptionHandler(BizException.class)
public Result exceptionHandler(BizException e) {
BizExceptionEnum exceptionEnum = e.getBizExceptionEnum();
Result result = new Result(exceptionEnum.getErrorCode(), exceptionEnum.getErrorMsg());
logger.error("business error", e);
return result;
}
// 这里就是通用的异常处理器了,所有预料之外的Exception异常都由这里处理
@ExceptionHandler(Exception.class)
public Result exceptionHandler(Exception e) {
Result result = new Result(1000, "网络繁忙,请稍后再试");
logger.error("application error", e);
return result;
}
}
在Controller里取出@ModelAttribute标注的方法返回的UserDetails对象:
RestController
@RequestMapping("/json/exam")
@Validated
public class ExamController {
@Autowired
private IExamService examService;
// ......
@PostMapping("/getExamListByOpInfo")
public Result> getExamListByOpInfo( @NotNull Date examOpDate,
@ModelAttribute("loginUserInfo") UserDetails userDetails) {
List resVos = examService.getExamListByOpInfo(examOpDate, userDetails);
Result> result = new Result(resVos);
return result;
}
}
当入参为examOpDate=2019-06-10时,Spring会使用我们上面@InitBinder注册的类型转换器将2019-06-10转换examOpDate对象:
@PostMapping("/getExamListByOpInfo")
public Result> getExamListByOpInfo(@NotNull Date examOpDate,
@ModelAttribute("loginUserInfo") UserDetails userDetails) {
List resVos = examService.getExamListByOpInfo(examOpDate, userDetails);
Result> result = new Result(resVos);
return result;
}
@ExceptionHandler标注的多个方法分别表示只处理特定的异常。这里需要注意的是当Controller抛出的某个异常多个@ExceptionHandler标注的方法都适用时,Spring会选择最具体的异常处理方法来处理,也就是说@ExceptionHandler(Exception.class)这里标注的方法优先级最低,只有当其它方法都不适用时,才会来到这里处理。
三、统一的响应处理
工程目录结构如下:
GlobalResponse是一个处理器类(handle),用来处理统一响应,代码如下:
package com.naylor.globalresponsebody.handler.response;
import com.alibaba.fastjson.JSON;
import com.naylor.globalresponsebody.handler.GlobalResponseEntity;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @BelongsProject: debris-app
* @BelongsPackage: com.naylor.globalresponsebody.response
* @Author: Chenml
* @CreateTime: 2020-09-02 15:26
* @Description: 全局响应
*/
@RestControllerAdvice("com.naylor")
public class GlobalResponse implements ResponseBodyAdvice
-
GlobalResponse类需要实现ResponseBodyAdvice接口
-
重写supports方法,可对响应进行过滤。实际开发中不一定所有的方法返回值都是相同的模板,这里可以根据MethodParameter进行过滤,此方法返回true则会走过滤,即会调用beforeBodyWrite方法,否则不会调用。
-
重写beforeBodyWrite方法,编写具体的响应数据逻辑
GlobalResponseEntity是一个实体类,用来封装统一响应和统一异常处理的返回值模板,具体代码如下:
@Data
@Accessors(chain = true)
public class GlobalResponseEntity implements Serializable {
private Boolean success = true;
private String code = "000000";
private String message = "request successfully";
private T data;
public GlobalResponseEntity() {
super();
}
public GlobalResponseEntity(T data) {
this.data = data;
}
public GlobalResponseEntity(String code, String message) {
this.code = code;
this.message = message;
this.data = null;
}
public GlobalResponseEntity(String code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public GlobalResponseEntity(Boolean success, String code, String message) {
this.success = success;
this.code = code;
this.message = message;
}
public GlobalResponseEntity(Boolean success, String code, String message, T data) {
this.success = success;
this.code = code;
this.message = message;
this.data = data;
}
public static GlobalResponseEntity<?> badRequest(String code, String message) {
return new GlobalResponseEntity<>(false, code, message);
}
public static GlobalResponseEntity<?> badRequest() {
return new GlobalResponseEntity<>(false, "404", "无法找到您请求的资源");
}
}
- GlobalResponseEntity类为一个泛型类,T为接口具体的返回数据
- success表示接口响应是否成功,更多情况下这个是业务叫法,和http状态码关系不大
- code表示接口响应状态码,可以根据特定业务场景自己定义
- message是描述信息
- 实际开发中code和mesage的具体值可以用枚举来维护
四、统一的异常处理
新增GlobalException类,编写统一异常处理。类上面添加
@RestControllerAdvice("com.naylor")和
@ResponseBody注解,ResponseBody用来对响应内容进行编排,如http状态码。代码如下:
@RestControllerAdvice("com.naylor")
@ResponseBody
@Slf4j
public class GlobalException {
/**
* 捕获一般异常
* 捕获未知异常
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ResponseEntity
引用
@RestControllerAdvice详解: https://zhuanlan.zhihu.com/p/73087879
@ResponseBodyAdvice详解:https://my.oschina.net/diamondfsd/blog/3069546/print