ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot 封装统一响应体(二)

2021-05-27 23:34:21  阅读:222  来源: 互联网

标签:ResultEnum 封装 SpringBoot public 响应 ServerResponse 注解 class


SpringBoot 封装统一响应体(一) 中介绍了使用统一结果类 ServerResponse 来封装统一响应体对象,需要用类似 ServerResponse.ok(data) 的形式进行响应。

在 Spring 3.2 中,新增了 @ControllerAdvice ,是一个 Controller 增强器,可对 Controller 中被 @RequestMapping 注解的方法加一些逻辑处理,最常用的就是异常处理。这篇文章介绍一种通过 @ControllerAdvice 和基于 AOP 实现的统一响应体方案。

1. 创建统一结果类

为了方便返回结果,这里还是创建一个统一结果类,其中 lombok 的 @AllArgsConstructor 注解用来向类中添加全参构造方法:

@Data
@AllArgsConstructor
public class ServerResponse {
    private Boolean success;
    private Integer code;
    private String message;
    private Object data;
}

2. 创建统一响应注解

统一响应注解是一个标记是否开启统一响应增强的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface BaseResponse {

}

3. 创建结果枚举类

结果枚举类还是复用之前的 ResultEnum

@Getter
@AllArgsConstructor
public enum ResultEnum {
    OK(true, 200, "成功"),
    BAD_REQUEST(false, 400, "参数错误"),
    UNAUTHORIZED(false, 401, "未授权,请登录"),
    NOT_FOUND(false, 404, "找不到请求的资源");

	private Boolean success;
    private Integer code;
    private String message;
}

这边使用了 @AllArgsConstructor 创建全参构造方法

4. 创建业务异常类

业务异常类是用于识别业务相关的异常,使用 ResponseCode 作为入参可以通过捕获异常获得返回的状态码信息:

@Data
@EqualsAndHashCode(callSuper = false)
public class BaseException extends RuntimeException{

    private ResponseCode code;

    public BaseException(ResponseCode code) {
        this.code = code;
    }

    public BaseException(Throwable cause, ResponseCode code) {
        super(cause);
        this.code = code;
    }
}

这里有几个注意点:

  • 自定义的异常可以继承 RuntimeException ,也可以继承 Exception 。Spring 对于 RuntimeException 类的异常才会进行事务回滚,所以我们一般自定义异常都继承该异常类;
  • @EqualsAndHashCode 注解会生成 equals(Object other)hashCode() 方法。有时候我们需要重写一个类的 @equals@hashcode 方法,就可以使用 @EqualsAndHashCode 注解;
  • @Data 相当于@Getter@Setter@RequiredArgsConstructor@ToString@EqualsAndHashCode 这5个注解的合集;
  • @EqualsAndHashCode(callSuper = false) 不调用父类的属性,如果子类属性相同,那么两个对象的 hashCode 值就相等;

在实际开发的时候,不同的异常除了通过状态码区分,还可以自定义不同的异常类,方便后面进行异常处理:

public class IllegalArgumentsException extends RuntimeException {
	// ...
}

public class UnauthorizedException extends RuntimeException{
	// ...
}

5. 创建异常处理类

在 Controller 中抛出异常,这里进行异常处理:

@ControllerAdvice(annotations = BaseResponse.class)
@Slf4j
public class GlobalExceptionHandler {
	/**
     * 处理 400 错误
     * @return
     */
    @ResponseBody // 不加 @ResponseBody 返回的是框架默认的响应体
    @ExceptionHandler(value = IllegalArgumentsException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ServerResponse handleIllegalArgumentsException() {
        log.info("参数错误");
        return new ServerResponse(
                ResultEnum.BAD_REQUEST.getSuccess(),
                ResultEnum.BAD_REQUEST.getCode(),
                ResultEnum.BAD_REQUEST.getMessage(),
                null
        );
    }

    /**
     * 处理 401 错误
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = UnauthorizedException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ServerResponse handleUnauthorizedException() {
        log.info("未授权,请登录");
        return new ServerResponse(
                ResultEnum.UNAUTHORIZED.getSuccess(),
                ResultEnum.UNAUTHORIZED.getCode(),
                ResultEnum.UNAUTHORIZED.getMessage(),
                null
        );
    }
}

这里需要注意 @ExceptionHandler 注解传入的参数可以是一个数组,但是传入的参数不能相同,也就是不能使用两个@ExceptionHandler去处理同一个异常。

还有一个问题,在自定义类上面使用 @ControllerAdvice 注解,为了能返回自己封装的响应体,需要给成员方法都加上 @ResponseBody 。如果不加 @ResponseBody 注解,例如下面的代码:

@ControllerAdvice(annotations = BaseResponse.class)
public class GlobalExceptionHandler {
	@ExceptionHandler(value = IllegalArgumentsException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ServerResponse handleIllegalArgumentsException() {
        return new ServerResponse(
                ResultEnum.BAD_REQUEST.getSuccess(),
                ResultEnum.BAD_REQUEST.getCode(),
                ResultEnum.BAD_REQUEST.getMessage(),
                null
        );
    }
}

响应的结果如下,可以看到是框架默认的响应体:
在这里插入图片描述
加上 @ResponseBody 之后就可以正常响应了:
在这里插入图片描述
顺便再解释一下 @ControllerAdvice@RestControllerAdvice 的区别。简单地说,@RestControllerAdvice 注解包含了 @ControllerAdvice 注解和 @ResponseBody 注解:

  • @RestController = @Controller + @ResponseBody
  • @RestControllerAdvice = @ControllerAdvice + @ResponseBody

如果自定义类上面添加了 @RestControllerAdvice 注解,返回自己封装的响应体就无需再用 @ResponseBody

@RestControllerAdvice(annotations = BaseResponse.class)
public class GlobalExceptionHandler {
	@ExceptionHandler(value = IllegalArgumentsException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ServerResponse handleIllegalArgumentsException() {
        return new ServerResponse(
                ResultEnum.BAD_REQUEST.getSuccess(),
                ResultEnum.BAD_REQUEST.getCode(),
                ResultEnum.BAD_REQUEST.getMessage(),
                null
        );
    }
}

6. 创建响应增强类

创建一个响应增强类 ServerResponseAdvice 实现 ResponseBodyAdvice 接口:

@ControllerAdvice(annotations = BaseResponse.class)
@Slf4j
public class ServerResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        log.info("returnType:" + returnType);
        log.info("converterType:" + converterType);
        return true;
    }

    @Override
    public ServerResponse beforeBodyWrite(
            Object body,
            MethodParameter returnType,
            MediaType selectedContentType,
            Class selectedConverterType,
            ServerHttpRequest request,
            ServerHttpResponse response
    ) {
        return new ServerResponse(
                ResultEnum.OK.getSuccess(),
                ResultEnum.OK.getCode(),
                ResultEnum.OK.getMessage(), // 成功展示默认提示信息
                body // 传给前端的参数
        );
    }
}

这里需要实现 supportsbeforeBodyWrite 两个方法:

  • supports 返回布尔值,用来判断哪些方法需要拦截(例如需要鉴权的请求),returnType 参数可以拿到调用的方法名;
  • beforeBodyWrite 就是在响应前的拦截操作,可以对响应头和响应体的内容进行修改,body 参数就是 Controller 方法中返回的内容;

如果不想在每一个接口都写一遍 ServerResponse.ok(data) 这样的代码,就可以使用 beforeBodyWrite 方法进行统一封装:

@RestControllerAdvice
public class ServerResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public ServerResponse beforeBodyWrite(
            Object body,
            MethodParameter returnType,
            MediaType selectedContentType,
            Class selectedConverterType,
            ServerHttpRequest request,
            ServerHttpResponse response
    ) {
        // 如果已经是 Result 类型,就直接返回
        if (body instanceof Result) {
            return body;
        }

        // 不是 Result 类型,就用 Result 包装后返回
        return Result.success(body);
    }
}

7. 在 Controller 中使用

这边写了一个简单的 Controller 方法,在类上面加上了封装的统一响应体注解:

@BaseResponse // 使用统一响应体注解
@RestController
@RequestMapping("ann-response")
public class AnnResponseController {
    @PostMapping("{userId}")
    public Integer pathValidate(@PathVariable Integer userId) {
        if(userId.equals(0)) {
            throw new IllegalArgumentsException();
        }
        if(userId.equals(1)) {
            throw new UnauthorizedException();
        }
        return userId;
    }
}

下面对接口进行测试:

http://localhost:8080/ann-response/0

在这里插入图片描述

http://localhost:8080/ann-response/1

在这里插入图片描述

http://localhost:8080/ann-response/2

在这里插入图片描述

8. 总结

通过上面的代码可以看出,这种封装方法是通过抛出特定的异常或者传特定的状态码,然后对异常进行处理,实际上跟 ServerResponse.okServerResponse.badRequest 用法异曲同工,但是可以对异常处理更精细。

然后在 beforeBodyWrite 中对响应内容进行了封装,因此无需在 Controller 方法写类似 ServerResponse.ok 之类的代码,但是成功的响应通常不止 200 请求,常见的还有 201204206 等等,因此上面封装的方法很难对成功的请求进行细分。

参考

SpringBoot统一响应体解决方案
spring-web-unified-response-demo
SpringBoot - @ControllerAdvice 处理异常
Lombok 的 @EqualsAndHashCode(callSuper = false) 的使用
SpringBoot ResponseBodyAdvice 接口实现自定义返回数据类型(响应头)
SpringBoot 使用 beforeBodyWrite 实现统一的接口返回类型

标签:ResultEnum,封装,SpringBoot,public,响应,ServerResponse,注解,class
来源: https://blog.csdn.net/weixin_43487782/article/details/117338171

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有