The old design
When developing an API, you need to define the data response results of the interface. The following is a very simple and direct Controller implementation method and response result definition.
@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) {
returnResponseBean.systemError(); }}}Copy the code
{
code: "".data: {}, // Can be an object or an array
msg: ""
}
Copy the code
From the code above, we can see that for each Controller method, there is a lot of duplicate code, and we should try to avoid duplicate code. After you remove the duplicate code, you get the following code, which is easy to understand.
@RestController
@RequestMapping("/users")
public class UserController {
@Inject
private UserService userService;
@GetRequest("/{userId:\\d+}")
public User signin(@PathVariable long userId) {
returnuserService.getUserBaseInfo(userId); }}Copy the code
One additional requirement made in the above implementation is that ServiceException needs to be defined as a subclass of RuntimeException, not Exception. Since ServiceException represents a ServiceException, this exception should be directly reported to the front end without any special handling. After subclassing RuntimeException, a large number of exception throw declarations are reduced, and special declarations in the Transactional @Transactional transaction are no longer required.
Unify the Controller return value format
In the process of development, I discovered the above structure
@ControllerAdvice
public class ControllerResponseHandler implements ResponseBodyAdvice<Object> {
private Logger logger = LogManager.getLogger(getClass());
@Override
public boolean supports(MethodParameter returnType, Class
> converterType) {
All return value types are supported
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class
> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof ResponseBean) {
return body;
} else {
// All results that do not return the ResponseBean structure are considered successful
returnResponseBean.success(body); }}}Copy the code
Unified Exception Handling
In the following code, ServiceException ServiceMessageException ValidatorErrorType FieldValidatorError are custom classes.
@ControllerAdvice
public class ControllerExceptionHandler {
private Logger logger = LogManager.getLogger(getClass());
private static final String logExceptionFormat = "[EXIGENCE] Some thing wrong with the system: %s";
/** * Custom exception */
@ExceptionHandler(ServiceMessageException.class)
public ResponseBean handleServiceMessageException(HttpServletRequest request, ServiceMessageException ex) {
logger.debug(ex);
return new ResponseBean(ex.getMsgCode(), ex.getMessage());
}
/** * Custom exception */
@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: entity class attribute check not through * such as: 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<FieldValidatorError> errors = new ArrayList<FieldValidatorError>();
for (FieldError error : result.getFieldErrors()) {
errors.add(toFieldValidatorError(error));
}
return ResponseBean.validatorError(errors);
}
/ * * * ConstraintViolationException: to check the method parameters directly, not by check. * such as: 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 : violation.getPropertyPath()) {
lastNode = node;
}
FieldValidatorError fieldNotValidError = new FieldValidatorError();
// fieldNotValidError.setType(ValidatorTypeMapping.toType(violation.getConstraintDescriptor().getAnnotation().annotationTyp e()));
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: abnormal data binding, the effect is similar to MethodArgumentNotValidException, for MethodArgumentNotValidException parent * /
@ExceptionHandler(BindException.class)
public ResponseBean handleBindException(HttpServletRequest request, BindException ex) {
logger.debug(ex);
return validatorErrors(ex.getBindingResult());
}
/** * Return type conversion error */
@ExceptionHandler(HttpMessageConversionException.class)
public ResponseBean exceptionHandle(HttpServletRequest request, HttpMessageConversionException ex) {
return internalServiceError(ex);
}
/** * The type that the client side expects to accept is inconsistent with the type that the server side returns. * Here interception is set, but it doesn't work. The cause needs to be further determined through the flow of the HTTP request. * /
@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 of the request header * The data type sent by the client is inconsistent with the data the server expects to receive */
@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);
}
/** * The data sent by the front end is not processed properly * for example, the day after tomorrow, we expect to receive a JSON data, but the front end sends XML data or a wrong JSON data */
@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);
}
/** * A problem caused when converting the returned result to the response data. * When using JSON as the result format, the possible cause is serialization errors. * It is currently known that this exception is caused when an object with no attributes is returned as a result. * /
@ExceptionHandler(HttpMessageNotWritableException.class)
public ResponseBean handlerHttpMessageNotWritableException(HttpServletRequest request, HttpMessageNotWritableException ex) {
return internalServiceError(ex);
}
/** * The request method does not support */
@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);
}
/** * Parameter types do not match */
@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));
}
/** * the required field */ is missing
@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));
}
/** * The file field */ was missing when the file was uploaded
@ExceptionHandler(MissingServletRequestPartException.class)
public ResponseBean exceptionHandle(HttpServletRequest request, MissingServletRequestPartException ex) {
logger.debug(ex);
return new ResponseBean(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
}
/** * The requested path does not exist */
@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);
}
/** * missing path parameter * Controller method defined@PathVariable(Required =true), but does not provide */ in the URL
@ExceptionHandler(MissingPathVariableException.class)
public ResponseBean exceptionHandle(HttpServletRequest request, MissingPathVariableException ex) {
return internalServiceError(ex);
}
/** * all other exceptions */
@ExceptionHandler()
public ResponseBean handleAll(HttpServletRequest request, Exception ex) {
return internalServiceError(ex);
}
private String codeToMessage(int code) {
//TODO this needs to be customized, each code will match a corresponding 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) { logger.error(String.format(logExceptionFormat, e.getMessage()), e); }}Copy the code
Through the above configuration, exceptions can be processed in a unified manner and returned results can be encapsulated in a unified manner.