SpringMVCController返回值及异常的统⼀处理⽅法
旧的设计⽅案
开发api的时候,需要先定义好接⼝的数据响应结果.如下是⼀个很简单直接的Controller实现⽅法及响应结果定义.
@RestController
@RequestMapping("/users")
public class UserController {
@Inject
private UserService userService;
@GetRequest("/{userId:\\d+}")
public ResponseBean signin(@PathVariable long userId) {
try {
User user = UserBaseInfo(userId);
return ResponseBean.success(user);
} catch (ServiceException e) {
return new Code(), 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) {
UserBaseInfo(userId);
}
}
在以上的实现中,还做了⼀个必要的要求,就是 ServiceException 需要定义为 RuntimeException的⼦类,⽽不是 Exception 的⼦类。由于 ServiceException 表⽰服务异常,⼀般发⽣这种异常是应该直接提⽰前端,⽽⽆需进⾏其他特殊处理的。在定义为 RuntimeException 的⼦类之后,会减少⼤量的异常抛出声明,⽽且不再需要在事务@Transactional 中进⾏特殊声明。
统⼀ Controller 返回值格式
在开发的过程中,我发现上⾯的结构
@ControllerAdvice
public class ControllerResponseHandler implements ResponseBodyAdvice<Object> {
private Logger logger = Logger(getClass());
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// ⽀持所有的返回值类型
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> 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 = Logger(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 MsgCode(), ex.getMessage());
}
/**
* ⾃定义异常
*/
@ExceptionHandler(ServiceException.class)
public ResponseBean handleServiceException(HttpServletRequest request, ServiceException ex) {
logger.debug(ex);
String message = MsgCode());
return new MsgCode(), message);
}
/**
* MethodArgumentNotValidException: 实体类属性校验不通过
* 如: listUsersValid(@RequestBody @Valid UserFilterOption option)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseBean handleMethodArgumentNotValid(HttpServletRequest request, MethodArgumentNotValidException ex) { logger.debug(ex);
return BindingResult());
}
private ResponseBean validatorErrors(BindingResult result) {
List<FieldValidatorError> errors = new ArrayList<FieldValidatorError>();
for (FieldError error : FieldErrors()) {
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<FieldValidatorError> errors = new ArrayList<FieldValidatorError>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
errors.add(toFieldValidatorError(violation));
}
return ResponseBean.validatorError(errors);
}
private FieldValidatorError toFieldValidatorError(ConstraintViolation<?> violation) {
Path.Node lastNode = null;
for (Path.Node node : PropertyPath()) {
lastNode = node;
}
FieldValidatorError fieldNotValidError = new FieldValidatorError();
// fieldNotValidError.ConstraintDescriptor().getAnno
tation().annotationType())); fieldNotValidError.setType(ValidatorErrorType.INVALID.value());
fieldNotValidError.Name());
fieldNotValidError.Message());
return fieldNotValidError;
}
private FieldValidatorError toFieldValidatorError(FieldError error) {
FieldValidatorError fieldNotValidError = new FieldValidatorError();
fieldNotValidError.setType(ValidatorErrorType.INVALID.value());
fieldNotValidError.Field());
fieldNotValidError.DefaultMessage());
return fieldNotValidError;
}
/**
* BindException: 数据绑定异常,效果与MethodArgumentNotValidException类似,为MethodArgumentNotValidException的⽗类 */
@ExceptionHandler(BindException.class)
public ResponseBean handleBindException(HttpServletRequest request, BindException ex) {
logger.debug(ex);
return BindingResult());
}
/**
* 返回值类型转化错误
*/
@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 ");
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().ContentType())
.append(" media type is not supported.").append(" Supported media types are ");
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().Method())
.append(" method is not supported for this request.").append(" Supported methods are ");
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.Name());
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.ParameterName());
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());mvc的controller
}
/**
* 请求路径不存在
*/
@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 <T extends Throwable> void logException(T e) {
<(String.format(logExceptionFormat, e.getMessage()), e);
}
}
通过上⾯的配置,可以有效地将异常进⾏统⼀的处理,同时对返回的结果进⾏统⼀的封装。
总结
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论