background

In today’s distributed, microservice prevailing, most projects have adopted the microservice framework, front and back end separation mode. The front end and the back end interact. The front end requests the URL path according to the convention and passes in relevant parameters. The back end server receives the request, processes services and returns data to the front end. It is necessary to maintain a set of perfect and standard interfaces, which can not only improve docking efficiency, but also make my code look more concise and elegant.

When using uniform return results, there is also a case where an error is reported by a program as a result of runtime exceptions, some of which we throw in our business and some of which we cannot predict in advance.

Therefore, we need to define a unified global exception, catch all exceptions in the Controller, handle them appropriately, and return them as a result.

Unified Interface Return

Define API return code enumeration classes

public enum ResultCode {
    /* Success status code */
    SUCCESS(200."Success"),
    
    /* Error status code */
    NOT_FOUND(404."Requested resource does not exist"),
    INTERNAL_ERROR(500."Server internal error"),
    PARAMETER_EXCEPTION(501."Request parameter verification exception"),
    
    /* Service status code */
    USER_NOT_EXIST_ERROR(10001."User does not exist"),;private Integer code;
    private String message;

    public Integer code(a) {
        return this.code;
    }

    public String message(a) {
        return this.message;
    }

    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message; }}Copy the code

Defines the API uniform return body for a normal response

@Data
public class Result<T> implements Serializable {
private Integer code;
    private String message;
    private boolean success = true;
    private T data;
    @JsonIgnore
    private ResultCode resultCode;

    private Result(a) {}public void setResultCode(ResultCode resultCode) {
        this.resultCode = resultCode;
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public Result(ResultCode resultCode, T data) {
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }

    public static <T> Result<T> success(a) {
        Result<T> result = new Result<>();
        result.setResultCode(ResultCode.SUCCESS);
        return result;
    }

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setResultCode(ResultCode.SUCCESS);
        result.setData(data);
            returnresult; }}Copy the code

The API uniform return body that defines the exception response

@Data
public class ErrorResult implements Serializable {
    private Integer code;
    private String message;
    private boolean success = false;
    @JsonIgnore
    private ResultCode resultCode;

    public static ErrorResult error(a) {
        ErrorResult result = new ErrorResult();
        result.setResultCode(ResultCode.INTERNAL_ERROR);
        return result;
    }

    public static ErrorResult error(String message) {
        ErrorResult result = new ErrorResult();
        result.setCode(ResultCode.INTERNAL_ERROR.code());
        result.setMessage(message);
        return result;
    }

    public static ErrorResult error(Integer code, String message) {
        ErrorResult result = new ErrorResult();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
    
    public static ErrorResult error(ResultCode resultCode, String message) {
        ErrorResult result = new ErrorResult();
        result.setResultCode(resultCode);
        result.setMessage(message)
        returnresult; }}Copy the code

Write custom annotations that wrap the returned results

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})  // Applies to methods and classes (interfaces)
@Documented
public @interface ResponseResult {
}
Copy the code

Define return result interceptors

@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
    /* Use the uniform return body identifier */
    private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // The method bean that is processing the request
        if (handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            // Get the current class
            finalClass<? > clazz = handlerMethod.getBeanType();// Get the current method
            final Method method = handlerMethod.getMethod();
            // Determine whether class objects are annotated
            if (clazz.isAnnotationPresent(ResponseResult.class)) {
                // Set the return body of the request, which needs to be wrapped, passed down, and determined in the ResponseBodyAdvice interface
                request.setAttribute(RESPONSE_RESULT_ANNOTATION, clazz.getAnnotation(ResponseResult.class));
            }
            // Determine whether methods are annotated
            else if (method.isAnnotationPresent(ResponseResult.class)) {
                // Set the return body of the request, which needs to be wrapped, passed down, and determined in the ResponseBodyAdvice interfacerequest.setAttribute(RESPONSE_RESULT_ANNOTATION, method.getAnnotation(ResponseResult.class)); }}return true; }}Copy the code

The WebMvc configuration class interceptor registrar adds the return result interceptor

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    /** * Add custom interceptor */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/ * *"); }}Copy the code

Write the response body processor

/** * handles the response body uniformly, wrapped in the result. success static method, * when used by the API interface can return the original type */
@RestControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
    /* Use the uniform return body identifier */
    private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";

    @Override
    public boolean supports(MethodParameter methodParameter, Class
       > aClass) {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(sra).getRequest();
        ResponseResult responseResult = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANNOTATION);
        // Determine whether the returned body needs processing
        returnresponseResult ! =null;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class
       > aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        // The exception response body returns the code+message body directly
        if (body instanceof ErrorResult) {
            return body;
        }
        // The normal response body returns the Result wrapped code+message+data body
        returnResult.success(body); }}Copy the code

Interface call

@api (" User Management ")
@RestController
@RequestMapping("user")
@ResponseResult  // Applies to all interfaces on the class
public class UserController {

    @Autowired
    private UserService userService;

    @ResponseResult  // apply to the method
    @apiOperation (" Query user by ID ")
    @GetMapping("one")
    public User selectOne(Long id) {
        // Since the ResponseResultHandler has already wrapped the returned data in result. success,
        // Return the original type
        return this.userService.queryById(id);
    }

    @ResponseResult
    @apiOperation (" Query all users ")
    @GetMapping("all")
    public List<User> selectAll(Page page) {
        // Since the ResponseResultHandler has already wrapped the returned data in result. success,
        // Return the original type
        return this.userService.queryAllByLimit(page); }}Copy the code

The test results


Global exception handling

Write a custom exception base class

@Data public class BaseException extends RuntimeException { private static final int BASE_EXCEPTION_CODE = ResultCode.INTERNAL_ERROR.code(); private static final String BASE_EXCEPTION_MESSAGE = ResultCode.INTERNAL_ERROR.message(); private Integer code; private String message; public BaseException() { super(BASE_EXCEPTION_MESSAGE); this.code = BASE_EXCEPTION_CODE; this.message = BASE_EXCEPTION_MESSAGE; } public BaseException(String message) { super(message); this.code = BASE_EXCEPTION_CODE; this.message = message; } public BaseException(ResultCode resultCode) { super(resultCode.message()); this.code = resultCode.code(); this.message = resultCode.message(); } public BaseException(Throwable cause) { super(cause); this.code = BASE_EXCEPTION_CODE; this.message = BASE_EXCEPTION_MESSAGE; } public BaseException(String message, Throwable cause) { super(message, cause); this.code = BASE_EXCEPTION_CODE; this.message = message; } public BaseException(Integer code, String message) { super(message); this.code = code; this.message = message; } public BaseException(Integer code, String message, Throwable cause) { super(message, cause); this.code = code; this.message = message; }}Copy the code

Write custom business exception classes

public class BizException extends BaseException {
    public BizException(ResultCode resultCode) {
        super(resultCode); }}Copy the code

Define global exception handling classes

The @ExceptionHandler annotation is used to uniformly handle a class of exceptions

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /** * Handle custom base exceptions */
    @ExceptionHandler(BaseException.class)
    public ErrorResult baseException(BaseException e) {
        if (StringUtils.isEmpty(e.getCode())) {
            return ErrorResult.error(e.getMessage());
        }
        return ErrorResult.error(e.getCode(), e.getMessage());
    }

    /** * Handle custom service exceptions */
    @ExceptionHandler(BizException.class)
    public ErrorResult bizException(BizException e) {
        if (StringUtils.isEmpty(e.getCode())) {
            return ErrorResult.error(e.getMessage());
        }
        return ErrorResult.error(e.getCode(), e.getMessage());
    }

    /** * Handle all exceptions except for non-custom exceptions */
    @ExceptionHandler(Exception.class)
    public ErrorResult handleException(Exception e) {
        log.error(e.getMessage(), e);
        return ErrorResult.error(e.getMessage());
    }
    
    Validation framework: Ignores the parameter exception handler */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ApiResult<String> parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
        log.error(e.getMessage(), e);
        return ErrorResult.error(PARAMETER_EXCEPTION, "Request parameters" + e.getParameterName() + "Can't be empty.");
    }
    
    Validation framework: Missing request body exception handler */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ErrorResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
        log.error(e.getMessage(), e);
        return ErrorResult.error(PARAMETER_EXCEPTION, "Parameter body cannot be empty");
    }

    Validation framework: Parameter Validation exception handler */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResult parameterExceptionHandler(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        // Get the exception information
        BindingResult exceptions = e.getBindingResult();
        // Determine if there is an error message in the exception, if there is, use the message in the exception, otherwise use the default message
        if (exceptions.hasErrors()) {
            List<ObjectError> errors = exceptions.getAllErrors();
            if(! errors.isEmpty()) {// All error parameters are listed here
                FieldError fieldError = (FieldError) errors.get(0);
                returnErrorResult.error(PARAMETER_EXCEPTION, fieldError.getDefaultMessage()); }}return ErrorResult.error(PARAMETER_EXCEPTION, "Request parameter verification exception"); }}Copy the code

Interface call

    @ResponseResult
    @GetMapping
    public User update(a) {
        // Non-custom runtime exception
        long id = 10 / 0;
        return userService.queryById(id);
    }

    @ResponseResult
    @PostMapping
    public User insert(a) {
        // Throw a custom base exception
        throw new BaseException();
    }

    @ResponseResult
    @DeleteMapping
    public boolean delete(a) {
        // Throw custom business exceptions
        throw new BizException(USER_NOT_EXIST_ERROR);
    }
Copy the code

The test results