Springboot API接口统一输出消息格式保持原接口返回值不变

导读:本篇文章讲解 Springboot API接口统一输出消息格式保持原接口返回值不变,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

环境:Springboot2.4.11


很多时候我们对接口的返回值都会做统一的处理,返回{code, message,data}等信息标识本次请求的处理结果,这统一的处理也都是在各自的Controller上做自行的处理。本篇内容告诉你如何通过ResponseBodyAdvice对象来实现对结果的统一处理,也就是说在Controller上我们不再对返回结果进行处理了,而是由统一的一个ControllerAdice Bean对象进行处理。这对我们的Controller接口来说可读性更强,也业务无关的东西一概不出现,同时代码也更加简洁。

ResponseBodyAdvice是什么

ResponseBodyAdvice类型的Bean对象允许在执行@ResponseBody或ResponseEntity控制器方法之后但在使用HttpMessageConverter写入正文之前自定义响应。实现可以直接向
RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver注册,或者更可能使用@ControllerAdvice进行注释,在这种情况下,它们都将被自动检测到。

定义统一返回对象

public class R {
	
  public R() {
  }

  private int code ;
	
  private String message ;
	
  private Object result ;
	
  private String errorDetails ;

  public R(int code, String message, Object result) {
    super();
    this.code = code;
    this.message = message;
    this.result = result;
  }
	
  public R(int code, String message, Object result, String errorDetails) {
    super();
    this.code = code;
    this.message = message;
    this.result = result;
    this.errorDetails = errorDetails ;
  }

  public R(int code, String message, String errorDetails) {
    super();
    this.code = code;
    this.message = message;
    this.errorDetails = errorDetails ;
  }
	
  public R(int code, String message) {
    super();
    this.code = code;
    this.message = message;
  }
  public static R success() {
    return success(null) ;
  }
	
  public static R success(Object data) {
    return success("成功", data) ;
  }
	
  public static R success(String message, Object data) {
    return new R(ResultCode.SUCCESS, message, data) ;
  }
	
  public static R failure() {
    return failure("失败") ;
  }
	
  public static R failure(Object data) {
    return failure("失败", data) ;
  }
	
  public static R failure(String message) {
    return failure(message, null) ;
  }
  public static R failure(int code, String message) {
    return new R(code, message) ;
  }
  
  public static R failure(String message, Object data) {
    return new R(ResultCode.FAILURE, message, data) ;
  }
  public static R failure(String message, String errorDetails) {
    return new R(ResultCode.FAILURE, message, errorDetails) ;
  }
	
  public static R error(String message, String errorDetails) {
    return new R(ResultCode.ERROR, message, errorDetails) ;
  }
  public static interface ResultCode {
    int SUCCESS = 0 ;
    int FAILURE = -1 ;
    int ERROR = 500 ;
  }
}

定义ResponseBodyAdvice

@RestControllerAdvice
public class ResponseResultControllerAdvice implements ResponseBodyAdvice<Object> {

  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    return returnType.getParameterType() != Void.TYPE 
        && (!returnType.hasMethodAnnotation(NoPack.class) 
        && !returnType.getMethod().getDeclaringClass().isAnnotationPresent(NoPack.class)) ;
	}

  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
    Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    if (body instanceof R) {
      return body ;
    }
    return R.success(body) ;
  }

}

上面的类非常简单

supports 方法判断当前的请求接口是否支持,在这里对于void,或是有@NoPack注解的都不进行处理。

beforeBodyWrite 该方法判断如果返回值类型本身就是R类型就之间进行输出了,否则就进行包装处理。

测试接口

@GetMapping("/get1")
public Users get1() {
  Users users = new Users() ;
  users.setAge(100) ;
  return users ;
}

Springboot API接口统一输出消息格式保持原接口返回值不变

包装成功!!!

修改测试接口

在接口中抛出一个异常

@GetMapping("/get1")
public Users get1() {
  Users users = new Users() ;
  users.setAge(100) ;
  System.out.println( 1 / 0 ) ;
  return users ;
}

Springboot API接口统一输出消息格式保持原接口返回值不变

当有异常的时候并不能被处理,这里我们需要结合统一异常处理的机制。

异常处理

@RestControllerAdvice
public class ExceptionControllerAdvice {

  private static final Logger logger = LoggerFactory.getLogger(ExceptionControllerAdvice.class) ;
	
  @ExceptionHandler(Exception.class)
  public R jsonErrorHandler(HttpServletRequest req, Exception e){
    String fullThrowMessage = getStackTrace(e);
    if (e instanceof ServletRequestBindingException) {
      return R.error("参数绑定异常,请检查接口入参: " + e.getMessage(), fullThrowMessage) ;
    }
    if (e instanceof ClassCastException) {
      return R.error("请检查入参是否正确", fullThrowMessage) ;
    }
    if (e instanceof MethodArgumentNotValidException) {
      return R.failure(((MethodArgumentNotValidException)e).getBindingResult().getAllErrors().stream().map(err -> err.getDefaultMessage()).collect(Collectors.toList())) ;
    }
    Throwable cause = e.getCause() ;
    if (cause != null) {
      if (cause instanceof SQLDataException) {
        return R.error("数据格式错误,请检查入参是否正确", fullThrowMessage) ;
      }
      if (cause instanceof NumberFormatException) {
        return R.error("数据格式错误,请检查!" + cause.getMessage(), fullThrowMessage) ;
      }
    }
    logger.error("未捕获的异常:{}", e) ;
    return R.error(e.getMessage(), fullThrowMessage) ;
  }
    
  private static String getStackTrace(final Throwable throwable) {
    final StringWriter sw = new StringWriter();
    final PrintWriter pw = new PrintWriter(sw, true);
    throwable.printStackTrace(pw);
    return sw.getBuffer().toString() ;
  }
}

在Springboot中可以通过@RestControllerAdvice注解标识一个类,让其可以统一处理未捕获的异常。查看这篇文章《Spring MVC 异常处理方式》该篇文章详细的讲解了异常的处理。

在进行测试

Springboot API接口统一输出消息格式保持原接口返回值不变

对于异常也统一的处理了。

实现原理

在Spring MVC中返回值的处理都是由
HandlerMethodReturnValueHandler对象进行处理的

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {
    protected <T> void writeWithMessageConverters(@Nullable T value, ...) {
        // 根据当前的请求获取客户端能产生的响应类型
        List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
        // 遍历所有的HttpMessageConverter对象
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                                                            (GenericHttpMessageConverter<?>) converter : null);
            if (genericConverter != null ?
                ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                converter.canWrite(valueType, selectedMediaType)) {
                // 在输出内容前先通过ResponseBodyAdvice处理内容信息
                body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                                                   (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                                                   inputMessage, outputMessage);
                if (body != null) {
                    Object theBody = body;
                    addContentDispositionHeader(inputMessage, outputMessage);
                    if (genericConverter != null) {
                        // ResponseBodyAdvice处理完后,最后通过GenericHttpMessageConverter输出内容到客户端了。
                        // 后续就是处理其它的比如拦截器的afterCompletion方法等操作了。
                        genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                    } else {
                        ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                    }
                } else {
                }
                return;
            }
        }
    }
}

完毕!!!

求关注+转发

 图片

图片

图片

图片

图片

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/79988.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!