SpringBoot统一标准响应格式及异常处理

    1springBootkafka,,;

    2SpringBoot线ThreadPoolTaskExecutor;

    3java后端接口API性能优化技巧

    4SpringBoot+MyBatis,,力。

一、概述

      在开发SpringBoot后端服务时,一般需要给前端统一的响应格式及异常处理,方便前端调试及配置错误提示等。比如:自定义Response结构,若每个开发者封装各自的Response结构,造成不一致,不利于前端处理,因此我们需要将响应格式统一起来,定义一个统一的标准响应格式。

二、全局统一响应

2.1、定义响应标准格式

一般至少如下三点:

        1、code:响应状态码,后端统一定义;

        2、message:响应的消息;

        3、data:响应返回数据。

例如:

{    "code": "C0001",    "message": "该用户已存在",    "data": null}

2.2、定义统一响应对象

/** * @Description: TODO:定义一统一的响应对象类 * @Author: yyalin * @CreateDate: 2022/10/26 18:09 * @Version: V1.0 */@Data@AllArgsConstructor@NoArgsConstructor@ApiModel(value = "定义一统一的响应对象")public class ResultVO<T> implements Serializable {    private static final long serialVersionUID = -2548645345465031121L;    @ApiModelProperty(value = "响应状态码")    private Integer code;    @ApiModelProperty(value = "响应的消息")    private String message;    @ApiModelProperty(value = "响应返回数据")    private T data;    /**     * 功能描述:定义统一返回数据     * @MethodName: success     * @MethodParam: [data]     * @Return: com.wonders.common.res.ResultVO     * @Author: yyalin     * @CreateDate: 2023/7/16 10:38     */    public static  ResultVO success(T data){        ResultVO resultVO = new ResultVO(                AppHttpCodeEnum.SUCCESS.getCode(),                AppHttpCodeEnum.SUCCESS.getMessage(),                data);        return resultVO;    }    /**     * 功能描述:定义统一返回数据并自定义一message消息     * @MethodName: success     * @MethodParam: [data]     * @Return: com.wonders.common.res.ResultVO     * @Author: yyalin     * @CreateDate: 2023/7/16 10:38     */    public static  ResultVO success(String message,T data){        ResultVO resultVO = new ResultVO(                AppHttpCodeEnum.SUCCESS.getCode(),                message,                data);        return resultVO;    }    /**     * 功能描述:抛出异常 返回错误信息     * @MethodName: fail     * @MethodParam: [message]     * @Return: com.wonders.common.res.ResultVO     * @Author: yyalin     * @CreateDate: 2023/7/16 10:55     */    public static  ResultVO fail(String message) {        return new ResultVO(                AppHttpCodeEnum.FAIL.getCode(),                message,                null);    }
}

2.3、定义响应状态码

/** * @Description: TODO:定义响应状态码 * @Author: yyalin * @CreateDate: 2023/7/16 10:14 * @Version: V1.0 */@Getter@AllArgsConstructorpublic enum  AppHttpCodeEnum {    // 成功    SUCCESS(200, "操作成功"),    FAIL(300,"操作失败"),    // 登录    NEED_LOGIN(401, "需要登录后操作"),    USERNAME_EXIST(501, "用户名已存在");
    private Integer code;  //响应状态码    private String message;   //响应的消息
}

2.4、controller层使用

    @ApiOperation(value="根据id获取学生信息", notes="getStudentInfo")    @GetMapping("/{studentId}")    public ResultVO getStudentInfo(@PathVariable long studentId){        //1、校验传来的参数是不是为空或者异常        return ResultVO.success(studentService.selectById(studentId));    }

三、统一异常处理

    例如:我们创建了三种自定义异常类:ClientException(客户端异常)、BusinessException(业务逻辑异常)和RemoteException(第三方服务异常)。这些异常类都继承自AbstractException,这是一个抽象的基类

3.1、自定义抽象异常

/** * @Description: TODO:自定义异常的抽象类 * @Author: yyalin * @CreateDate: 2023/7/16 11:04 * @Version: V1.0 */@Getterpublic abstract  class AbstractException  extends RuntimeException{    private final Integer code;    private final String message;
    public AbstractException(AppHttpCodeEnum appHttpCodeEnum, String message,                             Throwable throwable){        super(message,throwable);        this.code = appHttpCodeEnum.getCode();        this.message = Optional.ofNullable(message).orElse(appHttpCodeEnum.getMessage());    }}

3.2、具体的业务处理异常

/** * @desc:定义具体的业务异常处理 * @author :yyalin * @date:2019-11-27 */public class BusinessException extends AbstractException {
    public BusinessException(AppHttpCodeEnum appHttpCodeEnum, String message, Throwable throwable) {        super(appHttpCodeEnum, message, throwable);    }    public BusinessException(AppHttpCodeEnum appHttpCodeEnum) {        this(appHttpCodeEnum, null, null);    }    public BusinessException(AppHttpCodeEnum appHttpCodeEnum,String message) {        this(appHttpCodeEnum, message, null);    }}

3.3、全局异常处理

       SpringBoot提供了一个特殊的注解@RestControllerAdvice,允许我们创建全局异常处理类并且可以定义处理各种类型异常的方法。

主要有以下三种异常:

    1、MethodArgumentNotValidException:处理参数验证异常,并提供清晰的错误信息。

    2、AbstractException:处理之前定义的自定义异常。

    3、Throwable:作为最后的兜底,拦截所有其他异常。

/** * @desc:全局异常处理类 * @author :yyalin * @date:2019-11-27 */@RestControllerAdvice@Slf4j//@ControllerAdvicepublic class GlobalExceptionHandler {    //1、处理参数验证异常 MethodArgumentNotValidException    @SneakyThrows    @ExceptionHandler(value = MethodArgumentNotValidException.class)    public ResultVO handleValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {        BindingResult bindingResult = ex.getBindingResult();        FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors());        String exceptionStr = Optional.ofNullable(firstFieldError)                .map(FieldError::getDefaultMessage)                .orElse(StrUtil.EMPTY);        log.error("[{}] {} [ex] {}", request.getMethod(),"URL:", exceptionStr);        return ResultVO.fail(exceptionStr);    }
    // 处理自定义异常:AbstractException    @ExceptionHandler(value = {AbstractException.class})    public ResultVO handleAbstractException(HttpServletRequest request, AbstractException ex) {        String requestURL = "URL地址";        log.error("[{}] {} [ex] {}", request.getMethod(), requestURL, ex.toString());        return ResultVO.fail(ex.toString());    }
    // 兜底处理:Throwable    @ExceptionHandler(value = Throwable.class)    public ResultVO handleThrowable(HttpServletRequest request, Throwable throwable) {//        String requestURL = getUrl(request);        log.error("[{}] {} ", request.getMethod(), "URL地址", throwable);        return ResultVO.fail(AppHttpCodeEnum.FAIL.getMessage());    }}

        在启用全局异常处理功能后,不再需要在接口层手动使用try…catch来处理异常。倘若出现其他异常,它们也会被defaultErrorHandler拦截,从而确保一致地实施统一的返回格式。

四、自动包装类

      理论上到这我们已经实现了想要的统一后端响应格式了,但是有没有发现这里存在着一个缺点:每写一个接口都要调用ResultVO来包装返回结果,那是相当的繁琐。

4.1、ResponseBodyAdvice 

      在SpringBoot中,我们可以利用 ResponseBodyAdvice 来自动包装响应体。ResponseBodyAdvice可以拦截控制器(Controller)方法的返回值,允许我们统一处理返回值或响应体。这对于统一返回格式、加密、签名等场景非常有用。

源码分析:

public interface ResponseBodyAdvice {    boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType);
    @Nullable    T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class extends HttpMessageConverter>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);}

      1、supports方法用来判断是否支持advice功能,返回true表示支持,返回false则是不支持

2、beforeBodyWrite则是对返回的数据进行处理。

4.2、定义ResponseResultVO注解

    只有标注了这个注解的类或方法,才会对返回结果/响应统一格式。

/** * @Description: TODO:ResponseResult的注解类 * 只有标注了这个注解的类或方法,才会对返回结果/响应统一格式。 * @Author: yyalin * @CreateDate: 2023/7/16 11:57 * @Version: V1.0 */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})@Documentedpublic @interface ResponseResultVO {
}

4.3、定义实现类

/** * @Description: TODO:全局响应数据预处理器,使用RestControllerAdvice和ResponseBodyAdvice * 拦截Controller方法默认返回参数,统一处理响应体 * @Author: yyalin * @CreateDate: 2023/7/16 12:00 * @Version: V1.0 */@RestControllerAdvicepublic class RestResponseHandler implements ResponseBodyAdvice<Object> {    // 属性名称,用于记录是否标注了ResponseResultVO注解    public static final String RESPONSE_RESULTVO_ATTR= "RESPONSE_RESULTVO_ATTR";
    @Autowired    private ObjectMapper objectMapper;
    /**     * 判断是否需要执行beforeBodyWrite方法,true为执行;false为不执行     * @param returnType     * @param converterType     * @return     */    @Override    public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        HttpServletRequest request = requestAttributes.getRequest();        // 判断请求是否有注解标记        ResponseResultVO responseResultVO = (ResponseResultVO) request.getAttribute(RESPONSE_RESULTVO_ATTR);        return responseResultVO != null;    }
    /**     * 对返回值包装处理     * @param body     * @param returnType     * @param selectedContentType     * @param selectedConverterType     * @param request     * @param response     * @return     */    @SneakyThrows    @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,                                  Class extends HttpMessageConverter>> selectedConverterType,                                  ServerHttpRequest request, ServerHttpResponse response) {        // 已经返回的是Result类型对象了,直接返回,比如全局异常处理之后直接返回了        if (body instanceof ResultVO) {            return (ResultVO) body;        } else if (body instanceof  String) { // 如果Controller直接返回String时,需要转换为Json,因为强化的是RestController            return objectMapper.writeValueAsString(ResultVO.success(body));        }        return ResultVO.success(body);    }}

    注意:@RestControllerAdvice注解,是对@RestController注解的增强,如果要对@Controller注解增强,那就改为@ControllerAdvice注解即可。supports方法的处理逻辑是查找请求属性中有没有自定义的RESPONSE_RESULT_ATTR,如果有的话就返回true,支持对返回数据包装处理。

4.4、ResponseResultVO拦截器

        那这个RESPONSE_RESULTVO_ATTR是从哪里设置的呢?这其实就是跟上面提到的自定义注解有关了,增加了一个注解,那么必然要知道这个类或方法是否有引用了注解的,才能方便我们后续的操作,因此需要一个自定义拦截器,代码如下:

/** * @Description: TODO:ResponseResultVO拦截器 * @Author: yyalin * @CreateDate: 2023/7/16 12:11 * @Version: V1.0 */public class RestResponseInterceptor implements HandlerInterceptor {    // 属性名称,用于记录是否标注了ResponseResult注解    public static final String RESPONSE_RESULTVO_ATTR= "RESPONSE_RESULTVO_ATTR";
    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        if (handler instanceof HandlerMethod) {            final HandlerMethod handlerMethod = (HandlerMethod) handler;            final Class> clazz = handlerMethod.getBeanType();            final Method method = handlerMethod.getMethod();            // 判断是否在类对象上添加了注解            if (clazz.isAnnotationPresent(ResponseResultVO.class)) {                // 设置属性,值为该注解                request.setAttribute(RESPONSE_RESULTVO_ATTR, clazz.getAnnotation(ResponseResultVO.class));            } else if (method.isAnnotationPresent(ResponseResultVO.class)){                // 是否在方法上添加了注解                request.setAttribute(RESPONSE_RESULTVO_ATTR, method.getAnnotation(ResponseResultVO.class));            }        }        return true;    }}

4.5、拦截器注册到Spring中

/** * @Description: TODO:拦截器注册到Spring中 * @Author: yyalin * @CreateDate: 2023/7/16 12:16 * @Version: V1.0 */public class WebConfig implements WebMvcConfigurer {    /**     * 功能描述:SpringMVC 需要手动添加拦截器     * @MethodName: addInterceptors     * @MethodParam: [registry]     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/7/16 12:16     */    @Override    public void addInterceptors(InterceptorRegistry registry) {        //ResponseResultVO拦截器        RestResponseInterceptor respInterceptor = new RestResponseInterceptor();        registry.addInterceptor(respInterceptor);        WebMvcConfigurer.super.addInterceptors(registry);    }}

五、测试使用


/** * @Description: TODO * @Author: yyalin * @CreateDate: 2023/7/16 13:03 * @Version: V1.0 */@RestController@Api(tags="全局统一异常测试")public class ExceptionController {    @ApiOperation(value="测试", notes="test01")    @PostMapping("/test01")    @ResponseResultVO    public String test01() {        int i = 10 /1;        return "Ok";    }
}

加注解@ResponseResultVO

–不加注解@ResponseResultVO

参考:

https://blog.csdn.net/qq_45525848/article/details/128914930

https://blog.csdn.net/m0_68408835/article/details/131411554

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=14801,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?