Springboot学习第二天

随笔1个月前发布 僵尸
58 0 0

今天的学习内容是如何在项目中设计统一响应接口返回值,达到统一的格式

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

请求查询用户接口:

Springboot学习第二天

返回值:

Springboot学习第二天

 可以看到接口返回值已经按照我们的预期返回了code、message、data的数据结构。

再看看接口请求异常的情况。

请求添加用户接口:

Springboot学习第二天

username叫string的用户名在数据库中已存在,调用接口之后返回:

Springboot学习第二天

我们可以看到全局异常处理也成功了。

后面需要修改添加用户的接口代码,如果出现用户名称已存在是,正常情况下是跑的业务异常,不应该是提示:用户服务器内部错误,而应该是:用户名称重复。

好了,这个是项目实战中的异常处理专题,这套异常的设计,在实际的工作中还是比较有借鉴意义的。

 本学习项目来自苏三商城,该项目是知识星球:java突击队 的内部项目

 

 

 

 

 

 

 

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...