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

推荐文章:

    1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;

    2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;

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

    4、SpringBoot+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 <T> 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 <T> 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<T>     * @Author: yyalin     * @CreateDate: 2023/7/16 10:55     */    public static <T> ResultVO<T> fail(String message) {        return new ResultVO<T>(                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
//@ControllerAdvice
public 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<T> {
    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
 */
@RestControllerAdvice
public 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

https://blog.csdn.net/chisuisi5702/article/details/130219738?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-2-130219738-blog-130501182.235^v38^pc_relevant_sort_base1&spm=1001.2101.3001.4242.2&utm_relevant_index=5

 更多优秀文章,请扫码关注个人微信公众号或搜索“程序猿小杨”添加。          

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot提供了一种简单而强大的方式来处理应用程序中的异常,即统一异常处理。通过统一异常处理,我们可以捕获和处理应用程序中的所有异常,并返回自定义的错误响应。 在Spring Boot中,我们可以使用@ControllerAdvice注解来定义一个全局的异常处理类。这个类可以包含多个异常处理方法,每个方法对应一个具体的异常类型。当应用程序中抛出对应的异常时,Spring Boot会自动调用相应的异常处理方法进行处理。 下面是一个简单的示例代码,演示了如何使用@ControllerAdvice来实现统一异常处理: ```java @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException(Exception ex) { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setMessage("Internal Server Error"); errorResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setMessage("User Not Found"); errorResponse.setStatus(HttpStatus.NOT_FOUND.value()); return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); } // 其他异常处理方法... } ``` 在上面的代码中,我们定义了两个异常处理方法:handleException和handleUserNotFoundException。handleException方法用于处理所有未被其他异常处理方法捕获的异常,而handleUserNotFoundException方法用于处理UserNotFoundException异常。 在每个异常处理方法中,我们可以根据具体的业务需求,创建一个自定义的错误响应对象,并将其封装在ResponseEntity中返回给客户端。这样,无论是哪种异常,都可以得到统一的错误响应。 需要注意的是,为了使统一异常处理生效,我们还需要在应用程序的配置类上添加@EnableWebMvc注解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值