Spring MVC Controller返回值及异常的统一处理方法
旧的设计方案
开发api的时候,需要先定义好接口的数据响应结果.如下是一个很简单直接的Controller实现方法及响应结果定义.
@RestController @RequestMapping("/users") publicclassUserController{ @Inject privateUserServiceuserService; @GetRequest("/{userId:\\d+}") publicResponseBeansignin(@PathVariablelonguserId){ try{ Useruser=userService.getUserBaseInfo(userId); returnResponseBean.success(user); }catch(ServiceExceptione){ returnnewReponseBean(e.getCode(),e.getMsg()); }catch(Exceptione){ returnResponseBean.systemError(); } } }
{ code:"", data:{},//可以是对象或者数组 msg:"" }
从上面的代码,我们可以看到对于每个Controller方法,都会有很多重复的代码出现,我们应该设法去避免重复的代码。将重复的代码移除之后,可以得到如下的代码,简单易懂。
@RestController @RequestMapping("/users") publicclassUserController{ @Inject privateUserServiceuserService; @GetRequest("/{userId:\\d+}") publicUsersignin(@PathVariablelonguserId){ returnuserService.getUserBaseInfo(userId); } }
在以上的实现中,还做了一个必要的要求,就是ServiceException需要定义为RuntimeException的子类,而不是Exception的子类。由于ServiceException表示服务异常,一般发生这种异常是应该直接提示前端,而无需进行其他特殊处理的。在定义为RuntimeException的子类之后,会减少大量的异常抛出声明,而且不再需要在事务@Transactional中进行特殊声明。
统一Controller返回值格式
在开发的过程中,我发现上面的结构
@ControllerAdvice publicclassControllerResponseHandlerimplementsResponseBodyAdvice
统一异常处理
如下的代码中,ServiceExceptionServiceMessageExceptionValidatorErrorTypeFieldValidatorError均为自定义类。
@ControllerAdvice publicclassControllerExceptionHandler{ privateLoggerlogger=LogManager.getLogger(getClass()); privatestaticfinalStringlogExceptionFormat="[EXIGENCE]Somethingwrongwiththesystem:%s"; /** *自定义异常 */ @ExceptionHandler(ServiceMessageException.class) publicResponseBeanhandleServiceMessageException(HttpServletRequestrequest,ServiceMessageExceptionex){ logger.debug(ex); returnnewResponseBean(ex.getMsgCode(),ex.getMessage()); } /** *自定义异常 */ @ExceptionHandler(ServiceException.class) publicResponseBeanhandleServiceException(HttpServletRequestrequest,ServiceExceptionex){ logger.debug(ex); Stringmessage=codeToMessage(ex.getMsgCode()); returnnewResponseBean(ex.getMsgCode(),message); } /** *MethodArgumentNotValidException:实体类属性校验不通过 *如:listUsersValid(@RequestBody@ValidUserFilterOptionoption) */ @ExceptionHandler(MethodArgumentNotValidException.class) publicResponseBeanhandleMethodArgumentNotValid(HttpServletRequestrequest,MethodArgumentNotValidExceptionex){ logger.debug(ex); returnvalidatorErrors(ex.getBindingResult()); } privateResponseBeanvalidatorErrors(BindingResultresult){ Listerrors=newArrayList (); for(FieldErrorerror:result.getFieldErrors()){ errors.add(toFieldValidatorError(error)); } returnResponseBean.validatorError(errors); } /** *ConstraintViolationException:直接对方法参数进行校验,校验不通过。 *如:pageUsers(@RequestParam@Min(1)intpageIndex,@RequestParam@Max(100)intpageSize) */ @ExceptionHandler(ConstraintViolationException.class) publicResponseBeanhandleConstraintViolationException(HttpServletRequestrequest, ConstraintViolationExceptionex){ logger.debug(ex); // List errors=newArrayList (); for(ConstraintViolation>violation:ex.getConstraintViolations()){ errors.add(toFieldValidatorError(violation)); } returnResponseBean.validatorError(errors); } privateFieldValidatorErrortoFieldValidatorError(ConstraintViolation>violation){ Path.NodelastNode=null; for(Path.Nodenode:violation.getPropertyPath()){ lastNode=node; } FieldValidatorErrorfieldNotValidError=newFieldValidatorError(); //fieldNotValidError.setType(ValidatorTypeMapping.toType(violation.getConstraintDescriptor().getAnnotation().annotationType())); fieldNotValidError.setType(ValidatorErrorType.INVALID.value()); fieldNotValidError.setField(lastNode.getName()); fieldNotValidError.setMessage(violation.getMessage()); returnfieldNotValidError; } privateFieldValidatorErrortoFieldValidatorError(FieldErrorerror){ FieldValidatorErrorfieldNotValidError=newFieldValidatorError(); fieldNotValidError.setType(ValidatorErrorType.INVALID.value()); fieldNotValidError.setField(error.getField()); fieldNotValidError.setMessage(error.getDefaultMessage()); returnfieldNotValidError; } /** *BindException:数据绑定异常,效果与MethodArgumentNotValidException类似,为MethodArgumentNotValidException的父类 */ @ExceptionHandler(BindException.class) publicResponseBeanhandleBindException(HttpServletRequestrequest,BindExceptionex){ logger.debug(ex); returnvalidatorErrors(ex.getBindingResult()); } /** *返回值类型转化错误 */ @ExceptionHandler(HttpMessageConversionException.class) publicResponseBeanexceptionHandle(HttpServletRequestrequest, HttpMessageConversionExceptionex){ returninternalServiceError(ex); } /** *对应Http请求头的accept *客户器端希望接受的类型和服务器端返回类型不一致。 *这里虽然设置了拦截,但是并没有起到作用。需要通过http请求的流程来进一步确定原因。 */ @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) publicResponseBeanhandleHttpMediaTypeNotAcceptableException(HttpServletRequestrequest, HttpMediaTypeNotAcceptableExceptionex){ logger.debug(ex); StringBuildermessageBuilder=newStringBuilder().append("Themediatypeisnotacceptable.") .append("Acceptablemediatypesare"); ex.getSupportedMediaTypes().forEach(t->messageBuilder.append(t+",")); Stringmessage=messageBuilder.substring(0,messageBuilder.length()-2); returnnewResponseBean(HttpStatus.NOT_ACCEPTABLE.value(),message); } /** *对应请求头的content-type *客户端发送的数据类型和服务器端希望接收到的数据不一致 */ @ExceptionHandler(HttpMediaTypeNotSupportedException.class) publicResponseBeanhandleHttpMediaTypeNotSupportedException(HttpServletRequestrequest, HttpMediaTypeNotSupportedExceptionex){ logger.debug(ex); StringBuildermessageBuilder=newStringBuilder().append(ex.getContentType()) .append("mediatypeisnotsupported.").append("Supportedmediatypesare"); ex.getSupportedMediaTypes().forEach(t->messageBuilder.append(t+",")); Stringmessage=messageBuilder.substring(0,messageBuilder.length()-2); System.out.println(message); returnnewResponseBean(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),message); } /** *前端发送过来的数据无法被正常处理 *比如后天希望收到的是一个json的数据,但是前端发送过来的却是xml格式的数据或者是一个错误的json格式数据 */ @ExceptionHandler(HttpMessageNotReadableException.class) publicResponseBeanhandlerHttpMessageNotReadableException(HttpServletRequestrequest, HttpMessageNotReadableExceptionex){ logger.debug(ex); Stringmessage="ProblemsparsingJSON"; returnnewResponseBean(HttpStatus.BAD_REQUEST.value(),message); } /** *将返回的结果转化到响应的数据时候导致的问题。 *当使用json作为结果格式时,可能导致的原因为序列化错误。 *目前知道,如果返回一个没有属性的对象作为结果时,会导致该异常。 */ @ExceptionHandler(HttpMessageNotWritableException.class) publicResponseBeanhandlerHttpMessageNotWritableException(HttpServletRequestrequest, HttpMessageNotWritableExceptionex){ returninternalServiceError(ex); } /** *请求方法不支持 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) publicResponseBeanexceptionHandle(HttpServletRequestrequest,HttpRequestMethodNotSupportedExceptionex){ logger.debug(ex); StringBuildermessageBuilder=newStringBuilder().append(ex.getMethod()) .append("methodisnotsupportedforthisrequest.").append("Supportedmethodsare"); ex.getSupportedHttpMethods().forEach(m->messageBuilder.append(m+",")); Stringmessage=messageBuilder.substring(0,messageBuilder.length()-2); returnnewResponseBean(HttpStatus.METHOD_NOT_ALLOWED.value(),message); } /** *参数类型不匹配 */ @ExceptionHandler(MethodArgumentTypeMismatchException.class) publicResponseBeanmethodArgumentTypeMismatchExceptionHandler(HttpServletRequestrequest, MethodArgumentTypeMismatchExceptionex){ logger.debug(ex); Stringmessage="Theparameter'"+ex.getName()+"'shouldoftype'" +ex.getRequiredType().getSimpleName().toLowerCase()+"'"; FieldValidatorErrorfieldNotValidError=newFieldValidatorError(); fieldNotValidError.setType(ValidatorErrorType.TYPE_MISMATCH.value()); fieldNotValidError.setField(ex.getName()); fieldNotValidError.setMessage(message); returnResponseBean.validatorError(Arrays.asList(fieldNotValidError)); } /** *缺少必填字段 */ @ExceptionHandler(MissingServletRequestParameterException.class) publicResponseBeanexceptionHandle(HttpServletRequestrequest, MissingServletRequestParameterExceptionex){ logger.debug(ex); Stringmessage="Requiredparameter'"+ex.getParameterName()+"'isnotpresent"; FieldValidatorErrorfieldNotValidError=newFieldValidatorError(); fieldNotValidError.setType(ValidatorErrorType.MISSING_FIELD.value()); fieldNotValidError.setField(ex.getParameterName()); fieldNotValidError.setMessage(message); returnResponseBean.validatorError(Arrays.asList(fieldNotValidError)); } /** *文件上传时,缺少file字段 */ @ExceptionHandler(MissingServletRequestPartException.class) publicResponseBeanexceptionHandle(HttpServletRequestrequest,MissingServletRequestPartExceptionex){ logger.debug(ex); returnnewResponseBean(HttpStatus.BAD_REQUEST.value(),ex.getMessage()); } /** *请求路径不存在 */ @ExceptionHandler(NoHandlerFoundException.class) publicResponseBeanexceptionHandle(HttpServletRequestrequest,NoHandlerFoundExceptionex){ logger.debug(ex); Stringmessage="Noresourcefoundfor"+ex.getHttpMethod()+""+ex.getRequestURL(); returnnewResponseBean(HttpStatus.NOT_FOUND.value(),message); } /** *缺少路径参数 *Controller方法中定义了@PathVariable(required=true)的参数,但是却没有在url中提供 */ @ExceptionHandler(MissingPathVariableException.class) publicResponseBeanexceptionHandle(HttpServletRequestrequest,MissingPathVariableExceptionex){ returninternalServiceError(ex); } /** *其他所有的异常 */ @ExceptionHandler() publicResponseBeanhandleAll(HttpServletRequestrequest,Exceptionex){ returninternalServiceError(ex); } privateStringcodeToMessage(intcode){ //TODO这个需要进行自定,每个code会匹配到一个相应的msg return"Thecodeis"+code; } privateResponseBeaninternalServiceError(Exceptionex){ logException(ex); //dosomethingelse returnResponseBean.systemError(); } private voidlogException(Te){ logger.error(String.format(logExceptionFormat,e.getMessage()),e); } }
通过上面的配置,可以有效地将异常进行统一的处理,同时对返回的结果进行统一的封装。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。