热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

SpringMVCController返回值及异常的统一处理方法

这篇文章主要给大家介绍了关于SpringMVCController返回值及异常的统一处理方法,文中通过示例代码介绍的非常详细,对大家的学习或者使用SpringMVC具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

旧的设计方案

开发api的时候,需要先定义好接口的数据响应结果.如下是一个很简单直接的Controller实现方法及响应结果定义.

@RestController
@RequestMapping("/users")
public class UserController {

 @Inject
 private UserService userService;

 @GetRequest("/{userId:\\d+}")
 public ResponseBean signin(@PathVariable long userId) {
  try {
   User user = userService.getUserBaseInfo(userId);
   return ResponseBean.success(user);
  } catch (ServiceException e) {
   return new ReponseBean(e.getCode(), e.getMsg());
  } catch (Exception e) {
   return ResponseBean.systemError();
  }
 }
}
{
 code: "",
 data: {}, // 可以是对象或者数组
 msg: ""
}

从上面的代码,我们可以看到对于每个 Controller 方法,都会有很多重复的代码出现,我们应该设法去避免重复的代码。将重复的代码移除之后,可以得到如下的代码,简单易懂。

@RestController
@RequestMapping("/users")
public class UserController {
  
 @Inject
 private UserService userService;

 @GetRequest("/{userId:\\d+}")
 public User signin(@PathVariable long userId) {
  return userService.getUserBaseInfo(userId);
 }
}

在以上的实现中,还做了一个必要的要求,就是 ServiceException 需要定义为 RuntimeException的子类,而不是 Exception的子类。由于 ServiceException 表示服务异常,一般发生这种异常是应该直接提示前端,而无需进行其他特殊处理的。在定义为 RuntimeException 的子类之后,会减少大量的异常抛出声明,而且不再需要在事务@Transactional 中进行特殊声明。

统一 Controller 返回值格式

在开发的过程中,我发现上面的结构

@ControllerAdvice
public class ControllerResponseHandler implements ResponseBodyAdvice {
 
 private Logger logger = LogManager.getLogger(getClass());

 @Override
 public boolean supports(MethodParameter returnType, Class<&#63; extends HttpMessageConverter<&#63;>> converterType) {
  // 支持所有的返回值类型
  return true;
 }

 @Override
 public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
   Class<&#63; extends HttpMessageConverter<&#63;>> selectedConverterType, ServerHttpRequest request,
   ServerHttpResponse response) {
  if(body instanceof ResponseBean) {
   return body;
  } else {
   // 所有没有返回 ResponseBean 结构的结果均认为是成功的
   return ResponseBean.success(body);
  }
 }
}

统一异常处理

如下的代码中,ServiceException ServiceMessageException ValidatorErrorType FieldValidatorError 均为自定义类。

@ControllerAdvice
public class ControllerExceptionHandler {

 private Logger logger = LogManager.getLogger(getClass());

 private static final String logExceptiOnFormat= "[EXIGENCE] Some thing wrong with the system: %s";

 /**
  * 自定义异常
  */
 @ExceptionHandler(ServiceMessageException.class)
 public ResponseBean handleServiceMessageException(HttpServletRequest request, ServiceMessageException ex) {
  logger.debug(ex);
  return new ResponseBean(ex.getMsgCode(), ex.getMessage());
 }

 /**
  * 自定义异常
  */
 @ExceptionHandler(ServiceException.class)
 public ResponseBean handleServiceException(HttpServletRequest request, ServiceException ex) {
  logger.debug(ex);
  String message = codeToMessage(ex.getMsgCode());
  return new ResponseBean(ex.getMsgCode(), message);
 }

 /**
  * MethodArgumentNotValidException: 实体类属性校验不通过
  * 如: listUsersValid(@RequestBody @Valid UserFilterOption option)
  */
 @ExceptionHandler(MethodArgumentNotValidException.class)
 public ResponseBean handleMethodArgumentNotValid(HttpServletRequest request, MethodArgumentNotValidException ex) {
  logger.debug(ex);
  return validatorErrors(ex.getBindingResult());
 }

 private ResponseBean validatorErrors(BindingResult result) {
  List errors = new ArrayList();
  for (FieldError error : result.getFieldErrors()) {
   errors.add(toFieldValidatorError(error));
  }
  return ResponseBean.validatorError(errors);
 }

 /**
  * ConstraintViolationException: 直接对方法参数进行校验,校验不通过。
  * 如: pageUsers(@RequestParam @Min(1)int pageIndex, @RequestParam @Max(100)int pageSize)
  */
 @ExceptionHandler(ConstraintViolationException.class)
 public ResponseBean handleConstraintViolationException(HttpServletRequest request,
   ConstraintViolationException ex) {
  logger.debug(ex);
  // 
  List errors = new ArrayList();

  for (ConstraintViolation<&#63;> violation : ex.getConstraintViolations()) {
   errors.add(toFieldValidatorError(violation));
  }
  return ResponseBean.validatorError(errors);
 }

 private FieldValidatorError toFieldValidatorError(ConstraintViolation<&#63;> violation) {
  Path.Node lastNode = null;
  for (Path.Node node : violation.getPropertyPath()) {
   lastNode = node;
  }

  FieldValidatorError fieldNotValidError = new FieldValidatorError();
  // fieldNotValidError.setType(ValidatorTypeMapping.toType(violation.getConstraintDescriptor().getAnnotation().annotationType()));
  fieldNotValidError.setType(ValidatorErrorType.INVALID.value());
  fieldNotValidError.setField(lastNode.getName());
  fieldNotValidError.setMessage(violation.getMessage());
  return fieldNotValidError;
 }

 private FieldValidatorError toFieldValidatorError(FieldError error) {
  FieldValidatorError fieldNotValidError = new FieldValidatorError();
  fieldNotValidError.setType(ValidatorErrorType.INVALID.value());
  fieldNotValidError.setField(error.getField());
  fieldNotValidError.setMessage(error.getDefaultMessage());
  return fieldNotValidError;
 }

 /**
  * BindException: 数据绑定异常,效果与MethodArgumentNotValidException类似,为MethodArgumentNotValidException的父类
  */
 @ExceptionHandler(BindException.class)
 public ResponseBean handleBindException(HttpServletRequest request, BindException ex) {
  logger.debug(ex);
  return validatorErrors(ex.getBindingResult());
 }

 /**
  * 返回值类型转化错误
  */
 @ExceptionHandler(HttpMessageConversionException.class)
 public ResponseBean exceptionHandle(HttpServletRequest request,
   HttpMessageConversionException ex) {
  return internalServiceError(ex);
 }
 
 /**
  * 对应 Http 请求头的 accept
  * 客户器端希望接受的类型和服务器端返回类型不一致。
  * 这里虽然设置了拦截,但是并没有起到作用。需要通过http请求的流程来进一步确定原因。
  */
 @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
 public ResponseBean handleHttpMediaTypeNotAcceptableException(HttpServletRequest request,
   HttpMediaTypeNotAcceptableException ex) {
  logger.debug(ex);
  StringBuilder messageBuilder = new StringBuilder().append("The media type is not acceptable.")
    .append(" Acceptable media types are ");
  ex.getSupportedMediaTypes().forEach(t -> messageBuilder.append(t + ", "));
  String message = messageBuilder.substring(0, messageBuilder.length() - 2);

  return new ResponseBean(HttpStatus.NOT_ACCEPTABLE.value(), message);
 }

 /**
  * 对应请求头的 content-type
  * 客户端发送的数据类型和服务器端希望接收到的数据不一致
  */
 @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
 public ResponseBean handleHttpMediaTypeNotSupportedException(HttpServletRequest request,
   HttpMediaTypeNotSupportedException ex) {
   logger.debug(ex);
  StringBuilder messageBuilder = new StringBuilder().append(ex.getContentType())
    .append(" media type is not supported.").append(" Supported media types are ");
  ex.getSupportedMediaTypes().forEach(t -> messageBuilder.append(t + ", "));
  String message = messageBuilder.substring(0, messageBuilder.length() - 2);
  System.out.println(message);
  return new ResponseBean(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), message);
 }

 /**
  * 前端发送过来的数据无法被正常处理
  * 比如后天希望收到的是一个json的数据,但是前端发送过来的却是xml格式的数据或者是一个错误的json格式数据
  */
 @ExceptionHandler(HttpMessageNotReadableException.class)
 public ResponseBean handlerHttpMessageNotReadableException(HttpServletRequest request,
   HttpMessageNotReadableException ex) {
  logger.debug(ex);
  String message = "Problems parsing JSON";
  return new ResponseBean(HttpStatus.BAD_REQUEST.value(), message);
 }

 /**
  * 将返回的结果转化到响应的数据时候导致的问题。
  * 当使用json作为结果格式时,可能导致的原因为序列化错误。
  * 目前知道,如果返回一个没有属性的对象作为结果时,会导致该异常。
  */
 @ExceptionHandler(HttpMessageNotWritableException.class)
 public ResponseBean handlerHttpMessageNotWritableException(HttpServletRequest request,
   HttpMessageNotWritableException ex) {
  return internalServiceError(ex);
 }

 /**
  * 请求方法不支持
  */
 @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
 public ResponseBean exceptionHandle(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
  logger.debug(ex);
  StringBuilder messageBuilder = new StringBuilder().append(ex.getMethod())
    .append(" method is not supported for this request.").append(" Supported methods are ");

  ex.getSupportedHttpMethods().forEach(m -> messageBuilder.append(m + ","));
  String message = messageBuilder.substring(0, messageBuilder.length() - 2);
  return new ResponseBean(HttpStatus.METHOD_NOT_ALLOWED.value(), message);
 }

 /**
  * 参数类型不匹配
  */
 @ExceptionHandler(MethodArgumentTypeMismatchException.class)
 public ResponseBean methodArgumentTypeMismatchExceptionHandler(HttpServletRequest request,
   MethodArgumentTypeMismatchException ex) {
  logger.debug(ex);
  String message = "The parameter '" + ex.getName() + "' should of type '"
    + ex.getRequiredType().getSimpleName().toLowerCase() + "'";

  FieldValidatorError fieldNotValidError = new FieldValidatorError();
  fieldNotValidError.setType(ValidatorErrorType.TYPE_MISMATCH.value());
  fieldNotValidError.setField(ex.getName());
  fieldNotValidError.setMessage(message);

  return ResponseBean.validatorError(Arrays.asList(fieldNotValidError));
 }

 /**
  * 缺少必填字段
  */
 @ExceptionHandler(MissingServletRequestParameterException.class)
 public ResponseBean exceptionHandle(HttpServletRequest request,
   MissingServletRequestParameterException ex) {
  logger.debug(ex);
  String message = "Required parameter '" + ex.getParameterName() + "' is not present";

  FieldValidatorError fieldNotValidError = new FieldValidatorError();
  fieldNotValidError.setType(ValidatorErrorType.MISSING_FIELD.value());
  fieldNotValidError.setField(ex.getParameterName());
  fieldNotValidError.setMessage(message);

  return ResponseBean.validatorError(Arrays.asList(fieldNotValidError));
 }

 /**
  * 文件上传时,缺少 file 字段
  */
 @ExceptionHandler(MissingServletRequestPartException.class)
 public ResponseBean exceptionHandle(HttpServletRequest request, MissingServletRequestPartException ex) {
  logger.debug(ex);
  return new ResponseBean(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
 }

 /**
  * 请求路径不存在
  */
 @ExceptionHandler(NoHandlerFoundException.class)
 public ResponseBean exceptionHandle(HttpServletRequest request, NoHandlerFoundException ex) {
  logger.debug(ex);
  String message = "No resource found for " + ex.getHttpMethod() + " " + ex.getRequestURL();
  return new ResponseBean(HttpStatus.NOT_FOUND.value(), message);
 }

 /**
  * 缺少路径参数
  * Controller方法中定义了 @PathVariable(required=true) 的参数,但是却没有在url中提供
  */
 @ExceptionHandler(MissingPathVariableException.class)
 public ResponseBean exceptionHandle(HttpServletRequest request, MissingPathVariableException ex) {
  return internalServiceError(ex);
 }

 /**
  * 其他所有的异常
  */
 @ExceptionHandler()
 public ResponseBean handleAll(HttpServletRequest request, Exception ex) {
  return internalServiceError(ex);
 }

 private String codeToMessage(int code) {
  //TODO 这个需要进行自定,每个 code 会匹配到一个相应的 msg
  return "The code is " + code;
 }

 private ResponseBean internalServiceError(Exception ex) {
  logException(ex);
  // do something else
  return ResponseBean.systemError();
 }

 private  void logException(T e) {
  logger.error(String.format(logExceptionFormat, e.getMessage()), e);
 }
}

通过上面的配置,可以有效地将异常进行统一的处理,同时对返回的结果进行统一的封装。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。


推荐阅读
  • 本文探讨了 RESTful API 和传统接口之间的关键差异,解释了为什么 RESTful API 在设计和实现上具有独特的优势。 ... [详细]
  • 本文探讨了在通过 API 端点调用时,使用猫鼬(Mongoose)的 findOne 方法总是返回 null 的问题,并提供了详细的解决方案和建议。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 本文详细介绍了如何使用PHP检测AJAX请求,通过分析预定义服务器变量来判断请求是否来自XMLHttpRequest。此方法简单实用,适用于各种Web开发场景。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • XNA 3.0 游戏编程:从 XML 文件加载数据
    本文介绍如何在 XNA 3.0 游戏项目中从 XML 文件加载数据。我们将探讨如何将 XML 数据序列化为二进制文件,并通过内容管道加载到游戏中。此外,还会涉及自定义类型读取器和写入器的实现。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 解决MongoDB Compass远程连接问题
    本文记录了在使用阿里云服务器部署MongoDB后,通过MongoDB Compass进行远程连接时遇到的问题及解决方案。详细介绍了从防火墙配置到安全组设置的各个步骤,帮助读者顺利解决问题。 ... [详细]
  • 本文详细探讨了HTTP 500内部服务器错误的成因、解决方案及其在Web开发中的影响。通过对具体案例的分析,帮助读者理解并解决此类问题。 ... [详细]
  • 本文介绍了如何使用PHP代码实现微信平台的媒体素材上传功能,详细解释了API接口的使用方法和注意事项,确保文件路径正确以避免常见的错误。 ... [详细]
  • 本文详细介绍了Git分布式版本控制系统中远程仓库的概念和操作方法。通过具体案例,帮助读者更好地理解和掌握如何高效管理代码库。 ... [详细]
  • 本文介绍了如何利用npm脚本和concurrently工具,实现本地开发环境中多个监听服务的同时启动,包括HTTP服务、自动刷新、Sass和ES6支持。 ... [详细]
author-avatar
办事繁华_491
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有