今天的学习内容是如何在项目中设计统一响应接口返回值,达到统一的格式
1. 响应实体
我们首先要定义一个公共的接口响应实体,以后所有的接口返回值,都是返回的这个公共响应实体。
这样做的好处是可以统一返回值的风格,编译接口的维护。
需要包含3个关键的成员变量:
状态码
返回信息
数据
例如下面这样的:api请求响应实体
@AllArgsConstructor
@Data
public class ApiResult<T> {
/**
* 请求成功状态码
*/
public static final int OK = HttpStatus.HTTP_OK;
/**
* 接口返回码
*/
private int code;
/**
* 接口返回信息
*/
private String message;
/**
* 数据
*/
private T data;
}
这样用户请求接口之后,接口返回的数据结构都是一样的。
2. 响应实体工具类
为了更加优雅的创建相应实体类,我们可以增加一个专门的工具类。
这个工具类的职责是创建上面的ApiResult类的实例。
当然有两种情况:
成功
失败
我们处理的时候可以区分开: api请求响应实体处理工具类
public class ApiResultUtil {
private ApiResultUtil() {
}
/**
* 请求成功
*
* @param data 数据
* @param <T> 数据类型
* @return 接口相应实体
*/
public static <T> ApiResult<T> success(T data) {
return new ApiResult<>(ApiResult.OK, null, data);
}
/**
* 请求成功
*
* @param <T> 数据类型
* @return 接口相应实体
*/
public static <T> ApiResult<T> success() {
return success(null);
}
/**
* 请求成功
*
* @param code 返回码
* @param message 返回信息
* @param <T> 数据类型
* @return 接口相应实体
*/
public static <T> ApiResult<T> error(int code, String message) {
return new ApiResult<>(code, message, null);
}
}
ApiResultUtil工具类中包含了两个重载的success方法,主要是处理接口请求成功的情况。
而error方法,主要是为了处理接口请求出现异常的情况。
需要注意的是ApiResultUtil类有一个私有的无参构造方法,是为了防止调用者new这个类的实例对象的一种常规做法,很多JDK源码中都有类似的做法。
3. 业务异常
有了上面公共的响应实体类,我们可以先处理异常了。
但异常有两种:
系统异常
业务异常
系统异常我们在统一处理时,错误码都返回500没问题。
但如果有些业务异常,错误码都返回500,这种设计不太合理。
因此,我们需要增加一个专门的业务异常类:BusinessException。
AllArgsConstructor
@Data
public class BusinessException extends RuntimeException {
public static final long serialVersionUID = -6735897190745766939L;
/**
* 异常码
*/
private int code;
/**
* 具体异常信息
*/
private String message;
public BusinessException() {
super();
}
}
这个异常类继承了RuntimeException类,是一种运行时异常,后面好处理。
包含了两个成员变量:
code:表示异常码
message:表示异常信息
比如用户添加接口中,出现用户名称相同时,异常信息可以提示:用户名称重复。
4. 全局异常处理
接下来,我们可以统一处理全局异常了。
在Spring MVC中可以通过@RestControllerAdvice注解处理全局异常:
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 统一处理异常
*
* @param e 异常
* @return API请求响应实体
*/
@ExceptionHandler(Throwable.class)
public ApiResult handleException(Throwable e) {
if (e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
log.info("请求出现业务异常:", e);
return ApiResultUtil.error(businessException.getCode(), businessException.getMessage());
}
log.error("请求出现系统异常:", e);
return ApiResultUtil.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误");
}
}
创建了GlobalExceptionHandler类打上了@RestControllerAdvice注解,并且在handleException方法上打上了@ExceptionHandler注解,处理的异常是最底层的Throwable。
然后在该方法中需要判断一下异常类型,如果是后续的业务代码中抛出的业务异常,则使用业务异常的返回码和返回信息。
否则使用ApiResultUtil.error方法统一转换成服务器内部错误异常。
5. 正常接口响应处理
处理好全局异常之后,接下来,处理接口正常响应的情况,也需要把结果做封装。
在Spring MVC可以通过@ControllerAdvice注解实现这个功能。
@ControllerAdvice
public class GlobalApiResultHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
String requestURI = request.getRequestURI();
return matchUrl(requestURI);
}
private boolean matchUrl(String uri) {
if (StringUtils.isBlank(uri)) {
return false;
}
return uri.contains("/v1");
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof ApiResult) {
return (ApiResult) body;
}
return ApiResultUtil.success(body);
}
}
GlobalApiResultHandler类打上了@ControllerAdvice注解,并且实现了ResponseBodyAdvice接口。
重写了该接口的supports和beforeBodyWrite方法。
supports方法决定了beforeBodyWrite方法是否执行,只有supports方法返回true时,beforeBodyWrite方法才会执行。
在beforeBodyWrite方法中,将接口的返回值使用ApiResultUtil.success方法做封装,当然如果返回值已经是ApiResult类型,则直接返回无需重复封装。
在supports方法中,目前是根据uri中的请求路径做的判断,防止把一些html或者其他类型的页面请求也做封装了。
6 测试
接下来,测试一下接口的统一封装功能。
先看看接口请求成功的情况。
还是访问swagger地址:http://localhost:8011/swagger-ui/index.html
请求查询用户接口:
返回值:
可以看到接口返回值已经按照我们的预期返回了code、message、data的数据结构。
再看看接口请求异常的情况。
请求添加用户接口:
username叫string的用户名在数据库中已存在,调用接口之后返回:
我们可以看到全局异常处理也成功了。
后面需要修改添加用户的接口代码,如果出现用户名称已存在是,正常情况下是跑的业务异常,不应该是提示:用户服务器内部错误,而应该是:用户名称重复。
好了,这个是项目实战中的异常处理专题,这套异常的设计,在实际的工作中还是比较有借鉴意义的。
本学习项目来自苏三商城,该项目是知识星球:java突击队 的内部项目