Springboot-优雅配置接口响应和统一异常处理
Springboot-优雅配置接口响应和统一异常处理
一、前言
越来越多的项目开始基于前后端分离的模式进行开发,这对后端接口的报文格式便有了一定的要求。通常,我们会采用JSON格式作为前后端交换数据格式,从而减少沟通成本等。
@PostMapping("/user/add")
public ResponseInfo addUser(@RequestBody @Validated User user) {
userService.add(user);
return ResponseInfo.success();
}
我们看看这个代码,有什么问题。 我们返回了统一的封装结果集ResponseInfo
但是后面所有的controller
都这么写不太友好。
- 返回内容这么不够明确具体
- 所有
controller
这么写增加重复工作量
我们可以这么去优化:
Spirng 提供了 ResponseBodyAdvice
接口,支持在消息转换器执行转换之前,对接口的返回结果进行处理,再结合 @ControllerAdvice
注解即可轻松支持上述功能。
二、统一响应配置
准备文件:
ResponseCodeEnums.java 响应类型枚举类
ResponseInfo.java 响应数据封装类
ResponseResultHandler.java 响应信息增强类
开始
ResponseCodeEnums.java
@Getter
@AllArgsConstructor
public enum ResponseCodeEnums {
SUCCESS(200, "success"),
FAIL(500, "failed"),
HTTP_STATUS_200(200, "ok"),
HTTP_STATUS_400(400, "request error"),
HTTP_STATUS_401(401, "no authentication"),
HTTP_STATUS_403(403, "no authorities"),
HTTP_STATUS_500(500, "server error");
private final int code;
private final String message;
}
ResponseInfo.java
@Data
public class ResponseInfo<T> {
/**
* 状态码
*/
protected int code;
/**
* 响应信息
*/
protected String message;
/**
* 返回数据
*/
private T data;
public static <T> ResponseInfo<T> success() {
return new ResponseInfo<>();
}
public static <T> ResponseInfo<T> success(T data) {
return new ResponseInfo<>(data);
}
public static <T> ResponseInfo<T> fail(String message) {
return new ResponseInfo<>(ResponseCodeEnums.FAIL.getCode(), message);
}
public ResponseInfo() {
this.code = ResponseCodeEnums.SUCCESS.getCode();
this.message = ResponseCodeEnums.SUCCESS.getMessage();
}
public ResponseInfo(ResponseCodeEnums statusEnums) {
this.code = statusEnums.getCode();
this.message = statusEnums.getMessage();
}
/**
* 若没有数据返回,可以人为指定状态码和提示信息
*/
public ResponseInfo(int code, String msg) {
this.code = code;
this.message = msg;
}
/**
* 有数据返回时,状态码为200,默认提示信息为“操作成功!”
*/
public ResponseInfo(T data) {
this.data = data;
this.code = ResponseCodeEnums.SUCCESS.getCode();
this.message = ResponseCodeEnums.SUCCESS.getMessage();
}
/**
* 有数据返回,状态码为 200,人为指定提示信息
*/
public ResponseInfo(T data, String msg) {
this.data = data;
this.code = ResponseCodeEnums.SUCCESS.getCode();
this.message = msg;
}
}
ResponseResultHandler.java
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
log.info("supports:{}", returnType.getDeclaringClass().getName());
/**
* 排除swagger-ui请求返回数据增强
*/
return !returnType.getDeclaringClass().getName().contains("springfox");
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果是ResponseInfo数据类型就直接返回
if (body instanceof ResponseInfo) {
return body;
}
// 如果是空,则返回成功
else if (body == null) {
return ResponseInfo.success();
}
// 如果是异常类型就直接返回
else if (body instanceof Exception) {
return body;
}
// 如果是String类型则直接返回String类型
else if (body instanceof String) {
return body;
}
// 返回封装后的数据
else {
return ResponseInfo.success(body);
}
}
}
测试
@RestController
public class TestController {
@GetMapping("/test")
public void hello(){}
}
结果
{
"code": 200,
"message": "success",
"data": null
}
OK,成功地响应了结果,到此配置完成,下面开始配置统一异常处理。
三、全局异常配置
在日常开发中,我们通过try...catch的形式捕获异常,并进行处理。在SpringBoot中,我们可以通过RestControllerAdvice注解来定义全局异常处理,这样就无需每处都try...catch了。
示例:
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {
/**
* 参数格式异常处理
*/
@ExceptionHandler({IllegalArgumentException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseInfo<String> badRequestException(IllegalArgumentException ex) {
log.error("参数格式不合法:{}", ex.getMessage());
return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + "", "参数格式不符!");
}
/**
* 权限不足异常处理
*/
@ExceptionHandler({AccessDeniedException.class})
@ResponseStatus(HttpStatus.FORBIDDEN)
public ResponseInfo<String> badRequestException(AccessDeniedException ex) {
return new ResponseInfo<>(HttpStatus.FORBIDDEN.value() + "", ex.getMessage());
}
/**
* 参数缺失异常处理
*/
@ExceptionHandler({MissingServletRequestParameterException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseInfo<String> badRequestException(Exception ex) {
return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + "", "缺少必填参数!");
}
/**
* 空指针异常
*/
@ExceptionHandler(NullPointerException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseInfo<String> handleTypeMismatchException(NullPointerException ex) {
log.error("空指针异常,{}", ex.getMessage());
return ResponseInfo.fail("空指针异常");
}
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseInfo<String> handleUnexpectedServer(Exception ex) {
log.error("系统异常:", ex);
return ResponseInfo.fail("系统发生异常,请联系管理员");
}
/**
* 系统异常处理
*/
@ExceptionHandler(Throwable.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseInfo<String> exception(Throwable throwable) {
log.error("系统异常", throwable);
return new ResponseInfo<>(HttpStatus.INTERNAL_SERVER_ERROR.value() + "系统异常,请联系管理员!");
}
}
在上述方法中,对一些常见的异常进行了统一处理。通常情况下,根据业务需要还会定义业务异常,并对业务异常进行处理,大家可以根据自己项目中异常的使用情况进行拓展。
关于@RestControllerAdvice的几点说明:
- @RestControllerAdvice注解包含了@Component注解,会把被注解的类作为组件交给Spring来管理。
- @RestControllerAdvice注解包含了@ResponseBody注解,异常处理完之后给调用方输出一个JSON格式的封装数据。
- @RestControllerAdvice注解有一个basePackages属性,该属性用来拦截哪个包中的异常信息,一般不指定,拦截项目工程中的所有异常。
- 在方法上通过@ExceptionHandler注解来指定具体的异常,在方法中处理该异常信息,最后将结果通过统一的JSON结构体返回给调用者。
测试:
@RestController
public class TestController {
@GetMapping("/test")
public void hello(){
int i = 1 / 0;
}
}
结果:
{
"code": 500,
"message": "系统发生异常,请联系管理员",
"data": null
}
可以看到统一异常处理对空指针异常进行了拦截处理,并返回了ExceptionHandlerAdvice中定义的统一报文格式。
四、总结
在使用SpringBoot或其他项目中,统一的报文格式和统一的异常处理都是必须的。本篇文章介绍了基于SpringBoot的实现,如果你的项目中采用了其他的技术栈,则可考虑对应的处理方式。同时,日常中很多类似的功能都可以统一进行处理,避免大量无效的硬编码,提高工作效率。
转载自:https://juejin.cn/post/7245223437000867898