SpringBoot中如何统一接口返回与全局异常处理详解

目录

  • 背景
  • 统一接口返回
    • 定义API返回码枚举类
    • 定义正常响应的API统一返回体
    • 定义异常响应的API统一返回体
    • 编写包装返回结果的自定义注解
    • 定义返回结果拦截器
    • WebMvc配置类拦截器注册者添加返回结果拦截器
    • 编写响应体处理器
    • 接口调用
    • 测试结果
  • 全局异常处理
    • 编写自定义异常基类
    • 编写自定义业务异常类
    • 定义全局异常处理类
    • 接口调用
    • 测试结果
  • 总结

    背景
    在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。维护一套完善且规范的接口是非常有必要的, 这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。
    使用统一返回结果时,还有一种情况,就是程序的报错是由于运行时异常导致的结果,有些异常是我们在业务中抛出的,有些是无法提前预知。
    因此,我们需要定义一个统一的全局异常,在Controller捕获所有异常,并且做适当处理,并作为一种结果返回。

    统一接口返回

    定义API返回码枚举类

    public enum ResultCode {/* 成功状态码 */SUCCESS(200, "成功"),/* 错误状态码 */NOT_FOUND(404, "请求的资源不存在"),INTERNAL_ERROR(500, "服务器内部错误"),PARAMETER_EXCEPTION(501, "请求参数校验异常"),/* 业务状态码 */USER_NOT_EXIST_ERROR(10001, "用户不存在"),; private Integer code; private String message; public Integer code() {return this.code; }public String message() {return this.message; }ResultCode(Integer code, String message) {this.code = code; this.message = message; }}


    定义正常响应的API统一返回体
    @Datapublic class Result implements Serializable {private Integer code; private String message; private boolean success = true; private T data; @JsonIgnoreprivate ResultCode resultCode; private Result() {}public void setResultCode(ResultCode resultCode) {this.resultCode = resultCode; this.code = resultCode.code(); this.message = resultCode.message(); }public Result(ResultCode resultCode, T data) {this.code = resultCode.code(); this.message = resultCode.message(); this.data = https://www.it610.com/article/data; }public static Result success() {Result result = new Result<>(); result.setResultCode(ResultCode.SUCCESS); return result; }public static Result success(T data) {Result result = new Result<>(); result.setResultCode(ResultCode.SUCCESS); result.setData(data); return result; }}


    定义异常响应的API统一返回体
    @Datapublic class ErrorResult implements Serializable {private Integer code; private String message; private boolean success = false; @JsonIgnoreprivate ResultCode resultCode; public static ErrorResult error() {ErrorResult result = new ErrorResult(); result.setResultCode(ResultCode.INTERNAL_ERROR); return result; }public static ErrorResult error(String message) {ErrorResult result = new ErrorResult(); result.setCode(ResultCode.INTERNAL_ERROR.code()); result.setMessage(message); return result; }public static ErrorResult error(Integer code, String message) {ErrorResult result = new ErrorResult(); result.setCode(code); result.setMessage(message); return result; }public static ErrorResult error(ResultCode resultCode, String message) {ErrorResult result = new ErrorResult(); result.setResultCode(resultCode); result.setMessage(message)return result; }}


    编写包装返回结果的自定义注解
    @Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})//作用于方法和类(接口)上@Documentedpublic @interface ResponseResult {}


    定义返回结果拦截器
    @Componentpublic class ResponseResultInterceptor implements HandlerInterceptor {/* 使用统一返回体的标识 */private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION"; @Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 正在处理请求的方法beanif (handler instanceof HandlerMethod) {final HandlerMethod handlerMethod = (HandlerMethod) handler; // 获取当前类final Class clazz = handlerMethod.getBeanType(); // 获取当前方法final Method method = handlerMethod.getMethod(); // 判断是否在类对象上加了注解if (clazz.isAnnotationPresent(ResponseResult.class)) {// 设置该请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断request.setAttribute(RESPONSE_RESULT_ANNOTATION, clazz.getAnnotation(ResponseResult.class)); }// 判断是否在方法上加了注解else if (method.isAnnotationPresent(ResponseResult.class)) {// 设置该请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断request.setAttribute(RESPONSE_RESULT_ANNOTATION, method.getAnnotation(ResponseResult.class)); }}return true; }}


    WebMvc配置类拦截器注册者添加返回结果拦截器
    @Configurationpublic class WebMvcConfig implements WebMvcConfigurer {/*** 添加自定义拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/**"); }}


    编写响应体处理器
    /** * 统一处理响应体,用Result.success静态方法包装, * 在API接口使用时就可以直接返回原始类型 */@RestControllerAdvicepublic class ResponseResultHandler implements ResponseBodyAdvice {/* 使用统一返回体的标识 */private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION"; @Overridepublic boolean supports(MethodParameter methodParameter, Class> aClass) {ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(sra).getRequest(); ResponseResult responseResult = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANNOTATION); // 判断返回体是否需要处理return responseResult != null; }@Overridepublic Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {// 异常响应体则直接返回code+message的消息体if (body instanceof ErrorResult) {return body; }// 正常响应体则返回Result包装的code+message+data的消息体return Result.success(body); }}


    接口调用
    @Api("用户管理")@RestController@RequestMapping("user")@ResponseResult// 作用于类上,对所有接口有效public class UserController {@Autowiredprivate UserService userService; @ResponseResult// 作用于方法上@ApiOperation("根据ID查询用户")@GetMapping("one")public User selectOne(Long id) {// 由于在ResponseResultHandler中已经统一将返回数据用Result.success包装了,// 直接返回原始类型即可,代码更简洁return this.userService.queryById(id); }@ResponseResult@ApiOperation("查询所有用户")@GetMapping("all")public List selectAll(Page page) {// 由于在ResponseResultHandler中已经统一将返回数据用Result.success包装了,// 直接返回原始类型即可,代码更简洁return this.userService.queryAllByLimit(page); }}



    测试结果
    SpringBoot中如何统一接口返回与全局异常处理详解
    文章图片

    SpringBoot中如何统一接口返回与全局异常处理详解
    文章图片


    全局异常处理

    编写自定义异常基类
    @Datapublic class BaseException extends RuntimeException {private static final int BASE_EXCEPTION_CODE = ResultCode.INTERNAL_ERROR.code(); private static final String BASE_EXCEPTION_MESSAGE = ResultCode.INTERNAL_ERROR.message(); private Integer code; private String message; public BaseException() {super(BASE_EXCEPTION_MESSAGE); this.code = BASE_EXCEPTION_CODE; this.message = BASE_EXCEPTION_MESSAGE; }public BaseException(String message) {super(message); this.code = BASE_EXCEPTION_CODE; this.message = message; }public BaseException(ResultCode resultCode) {super(resultCode.message()); this.code = resultCode.code(); this.message = resultCode.message(); }public BaseException(Throwable cause) {super(cause); this.code = BASE_EXCEPTION_CODE; this.message = BASE_EXCEPTION_MESSAGE; }public BaseException(String message, Throwable cause) {super(message, cause); this.code = BASE_EXCEPTION_CODE; this.message = message; }public BaseException(Integer code, String message) {super(message); this.code = code; this.message = message; }public BaseException(Integer code, String message, Throwable cause) {super(message, cause); this.code = code; this.message = message; }}


    编写自定义业务异常类
    public class BizException extends BaseException {public BizException(ResultCode resultCode) {super(resultCode); }}


    定义全局异常处理类
    通过@ExceptionHandler注解来统一处理某一类异常
    @Slf4j@RestControllerAdvicepublic class GlobalExceptionHandler {/*** 统一处理自定义基础异常*/@ExceptionHandler(BaseException.class)public ErrorResult baseException(BaseException e) {if (StringUtils.isEmpty(e.getCode())) {return ErrorResult.error(e.getMessage()); }return ErrorResult.error(e.getCode(), e.getMessage()); }/*** 统一处理自定义业务异常*/@ExceptionHandler(BizException.class)public ErrorResult bizException(BizException e) {if (StringUtils.isEmpty(e.getCode())) {return ErrorResult.error(e.getMessage()); }return ErrorResult.error(e.getCode(), e.getMessage()); }/*** 统一处理非自定义异常外的所有异常*/@ExceptionHandler(Exception.class)public ErrorResult handleException(Exception e) {log.error(e.getMessage(), e); return ErrorResult.error(e.getMessage()); }/*** 兼容Validation校验框架:忽略参数异常处理器*/@ExceptionHandler(MissingServletRequestParameterException.class)public ApiResult parameterMissingExceptionHandler(MissingServletRequestParameterException e) {log.error(e.getMessage(), e); return ErrorResult.error(PARAMETER_EXCEPTION, "请求参数 " + e.getParameterName() + " 不能为空"); }/*** 兼容Validation校验框架:缺少请求体异常处理器*/@ExceptionHandler(HttpMessageNotReadableException.class)public ErrorResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {log.error(e.getMessage(), e); return ErrorResult.error(PARAMETER_EXCEPTION, "参数体不能为空"); }/*** 兼容Validation校验框架:参数效验异常处理器*/@ExceptionHandler(MethodArgumentNotValidException.class)public ErrorResult parameterExceptionHandler(MethodArgumentNotValidException e) {log.error(e.getMessage(), e); // 获取异常信息BindingResult exceptions = e.getBindingResult(); // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息if (exceptions.hasErrors()) {List errors = exceptions.getAllErrors(); if (!errors.isEmpty()) {// 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可FieldError fieldError = (FieldError) errors.get(0); return ErrorResult.error(PARAMETER_EXCEPTION, fieldError.getDefaultMessage()); }}return ErrorResult.error(PARAMETER_EXCEPTION, "请求参数校验异常"); }}

    接口调用
    @ResponseResult@GetMappingpublic User update() {// 非自定义的运行时异常long id = 10 / 0; return userService.queryById(id); }@ResponseResult@PostMappingpublic User insert() {// 抛出自定义的基础异常throw new BaseException(); }@ResponseResult@DeleteMappingpublic boolean delete() {// 抛出自定义的业务异常throw new BizException(USER_NOT_EXIST_ERROR); }

    测试结果
    SpringBoot中如何统一接口返回与全局异常处理详解
    文章图片

    SpringBoot中如何统一接口返回与全局异常处理详解
    文章图片

    SpringBoot中如何统一接口返回与全局异常处理详解
    文章图片

    【SpringBoot中如何统一接口返回与全局异常处理详解】
    总结 到此这篇关于SpringBoot中如何统一接口返回与全局异常处理的文章就介绍到这了,更多相关SpringBoot统一接口返回内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

      推荐阅读