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.