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);
//
Listerrors=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();
}
privatevoidlogException(Te){
logger.error(String.format(logExceptionFormat,e.getMessage()),e);
}
}
通过上面的配置,可以有效地将异常进行统一的处理,同时对返回的结果进行统一的封装。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。