Spring Boot's non-invasive implementation of the API interface returns a response in a unified JSON format

Preface: In a project, if the API interface returns inconsistent responses, inexplicable bugs will appear in the scene where the front and back ends are separated, and the workload is not small when all interfaces are modified, so we adopt the non-intrusive method The solution is to implement the API interface to return the response in a unified JSON format.

Define return json body

{
    
      
    "code": 200,  // 状态码
    "message": "success",  // 返回信息描述
    "data": {
    
    }  // 返回数据
}  

Define JavaBean fields

@Getter  
@ToString  
public class Result<T> {
    
      
    /** 业务错误码 */  
    private Integer code;  
    /** 信息描述 */  
    private String message;  
    /** 返回参数 */  
    private T data;  
  
    private Result(ResultStatus resultStatus, T data) {
    
      
        this.code = resultStatus.getCode();  
        this.message = resultStatus.getMessage();  
        this.data = data;  
    }  
  
    /** 业务成功返回业务代码和描述信息 */  
    public static Result<Void> success() {
    
      
        return new Result<Void>(ResultStatus.SUCCESS, null);  
    }  
  
    /** 业务成功返回业务代码,描述和返回的参数 */  
    public static <T> Result<T> success(T data) {
    
      
        return new Result<T>(ResultStatus.SUCCESS, data);  
    }  
  
    /** 业务成功返回业务代码,描述和返回的参数 */  
    public static <T> Result<T> success(ResultStatus resultStatus, T data) {
    
      
        if (resultStatus == null) {
    
      
            return success(data);  
        }  
        return new Result<T>(resultStatus, data);  
    }  
  
    /** 业务异常返回业务代码和描述信息 */  
    public static <T> Result<T> failure() {
    
      
        return new Result<T>(ResultStatus.INTERNAL_SERVER_ERROR, null);  
    }  
  
    /** 业务异常返回业务代码,描述和返回的参数 */  
    public static <T> Result<T> failure(ResultStatus resultStatus) {
    
      
        return failure(resultStatus, null);  
    }  
  
    /** 业务异常返回业务代码,描述和返回的参数 */  
    public static <T> Result<T> failure(ResultStatus resultStatus, T data) {
    
      
        if (resultStatus == null) {
    
      
            return new Result<T>(ResultStatus.INTERNAL_SERVER_ERROR, null);  
        }  
        return new Result<T>(resultStatus, data);  
    }  
}

Here we have simply implemented the unified JSON format, but we also found a problem. If we want to return the unified JSON format, we need to return the Result. I obviously can return the Object. Why do we need to repeat the work? Is there a solution? , Of course there are, let's start optimizing our code.

Optimized and perfected

We all know that using the @ResponseBody annotation will serialize the returned Object into a JSON string. Let’s start with this. It’s about assigning the Object to the Result before serialization. You can observe org.springframework.web.servlet. mvc.method.annotation.ResponseBodyAdvice and org.springframework.web.bind.annotation.ResponseBody.

@ResponseBody inheritance class

We have decided to start with the @ResponseBody annotation and create an annotation class that inherits @ResponseBody, which can be marked on classes and methods so that we can use it freely

@Retention(RetentionPolicy.RUNTIME)  
@Target({
    
    ElementType.TYPE, ElementType.METHOD})  
@Documented  
@ResponseBody  
public @interface ResponseResultBody {
    
      
  
}  

ResponseBodyAdvice inheritance class

@RestControllerAdvice  
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {
    
      
  
    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;  
  
    /**  
     * 判断类或者方法是否使用了 @ResponseResultBody  
     */  
    @Override  
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    
      
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);  
    }  
  
    /**  
     * 当类或者方法使用了 @ResponseResultBody 就会调用这个方法  
     */  
    @Override  
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
      
        // 防止重复包裹的问题出现  
        if (body instanceof Result) {
    
      
            return body;  
        }  
        return Result.success(body);  
    }  
}  

Isn't it amazing that the JSON format can be unified by directly returning the Object, instead of returning the Result object for each return, it is perfect to let SpringMVC help us with unified management directly.

If you only want to look at the interface, helloError and helloMyError are the interfaces that will directly throw exceptions. I don’t seem to have unified processing for exception returns.

Unified Return JSON Format Advanced - Exception Handling (@ExceptionHandler))

Exception handling @ResponseStatus (not recommended)
The usage of @ResponseStatus is as follows, which can be used on Controller classes, Controller methods and Exception classes, but the workload is still quite large

@RestController  
@RequestMapping("/error")  
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Java的异常")  
public class HelloExceptionController {
    
      
  
    private static final HashMap<String, Object> INFO;  
  
    static {
    
      
        INFO = new HashMap<String, Object>();  
        INFO.put("name", "galaxy");  
        INFO.put("age", "70");  
    }  
  
    @GetMapping()  
    public HashMap<String, Object> helloError() throws Exception {
    
      
        throw new Exception("helloError");  
    }  
  
    @GetMapping("helloJavaError")  
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Java的异常")  
    public HashMap<String, Object> helloJavaError() throws Exception {
    
      
        throw new Exception("helloError");  
    }  
  
    @GetMapping("helloMyError")  
    public HashMap<String, Object> helloMyError() throws Exception {
    
      
        throw new MyException();  
    }  
}  
  
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "自己定义的异常")  
class MyException extends Exception {
    
      
  
}  

Global exception handling @ExceptionHandler (recommended)
to modify the ResponseResultBodyAdvice class, the code is a bit too much

@Slf4j  
@RestControllerAdvice  
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {
    
      
  
    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;  
  
    /** 判断类或者方法是否使用了 @ResponseResultBody */  
    @Override  
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    
      
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);  
    }  
  
    /** 当类或者方法使用了 @ResponseResultBody 就会调用这个方法 */  
    @Override  
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
      
        if (body instanceof Result) {
    
      
            return body;  
        }  
        return Result.success(body);  
    }  
  
  
    /**  
     * 提供对标准Spring MVC异常的处理  
     *  
     * @param ex      the target exception  
     * @param request the current request  
     */  
    @ExceptionHandler(Exception.class)  
    public final ResponseEntity<Result<?>> exceptionHandler(Exception ex, WebRequest request) {
    
      
        log.error("ExceptionHandler: {}", ex.getMessage());  
        HttpHeaders headers = new HttpHeaders();  
        if (ex instanceof ResultException) {
    
      
            return this.handleResultException((ResultException) ex, headers, request);  
        }  
        // TODO: 2019/10/05 galaxy 这里可以自定义其他的异常拦截  
        return this.handleException(ex, headers, request);  
    }  
  
    /** 对ResultException类返回返回结果的处理 */  
    protected ResponseEntity<Result<?>> handleResultException(ResultException ex, HttpHeaders headers, WebRequest request) {
    
      
        Result<?> body = Result.failure(ex.getResultStatus());  
        HttpStatus status = ex.getResultStatus().getHttpStatus();  
        return this.handleExceptionInternal(ex, body, headers, status, request);  
    }  
  
    /** 异常类的统一处理 */  
    protected ResponseEntity<Result<?>> handleException(Exception ex, HttpHeaders headers, WebRequest request) {
    
      
        Result<?> body = Result.failure();  
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;  
        return this.handleExceptionInternal(ex, body, headers, status, request);  
    }  
  
    /**  
     * org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler#handleExceptionInternal(java.lang.Exception, java.lang.Object, org.springframework.http.HttpHeaders, org.springframework.http.HttpStatus, org.springframework.web.context.request.WebRequest)  
     * <p>  
     * A single place to customize the response body of all exception types.  
     * <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE}  
     * request attribute and creates a {@link ResponseEntity} from the given  
     * body, headers, and status.  
     */  
    protected ResponseEntity<Result<?>> handleExceptionInternal(  
            Exception ex, Result<?> body, HttpHeaders headers, HttpStatus status, WebRequest request) {
    
      
  
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
    
      
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);  
        }  
        return new ResponseEntity<>(body, headers, status);  
    }  
}

Guess you like

Origin blog.csdn.net/weixin_44011409/article/details/111829712