ResultDemo, the interface returns objects uniformly

If the content returned by the interface is not formatted properly, which can cause problems for front-end developers, let’s make some optimizations to the interface.

content

  • Create the uniformly returned object Result
  • Use annotations to make the implementation more elegant
  • Combined with global exception catching

Create the uniformly returned object Result

package com.cc.api;

import java.io.Serializable;

/** * returns the object *@author cc
 * @dateThe 2021-07-12 10:10 * /
public class Result<T> implements Serializable {
    // Customize the status code
    private Integer code;
    If the interface fails, exception information will be stored
    private String msg;
    // Return the data body
    private T data;
    // The interface was successfully detected. Extended field, the foreground can use this interface to determine whether the interface is normal, or through the code status code
    private boolean success;
    private static final long serialVersionUID = 1L;

    public Result(a) {}

    public Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    The <T> between public and the return value specifies that this is a generic method, so that variables of type T can be used within the method *@author cc
     * @dateThe 2021-07-12 10:11 * /
    public static <T> Result<T> success(a) {
        return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), null);
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), data);
    }

    /** * Failed to request *@param msg:
     * @author cc
     * @dateThe 2021-07-12 10:11 * /
    public static <T> Result<T> failed(String msg) {
        return new Result<>(ResultCode.FAILED.getCode(), msg, null);
    }

    public static <T> Result<T> failed(String msg, T data) {
        return new Result<>(ResultCode.FAILED.getCode(), msg, data);
    }

    public static <T> Result<T> failed(ResultCode errorCode) {
        return new Result<>(errorCode.getCode(), errorCode.getMsg(), null);
    }

    public static <T> Result<T> failed(ResultCode errorCode, T data) {
        return newResult<>(errorCode.getCode(), errorCode.getMsg(), data); }...public boolean isSuccess(a) {
        return this.code == ResultCode.SUCCESS.getCode();
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    @Override
    public String toString(a) {
        return "Result{" + "code=" + code +
                ", msg='" + msg + '\' ' +
                ", data=" + data +
                ", success=" + success +
                '} '; }}Copy the code
Custom state code table
package com.cc.api;

/** * the state code table returned by the APi *@author cc
 * @dateVerily, 2021-07-12 * /
public enum ResultCode {
    /** * API status code management *@author cc
     * @dateThe 2021-07-12 10:00 * /
    SUCCESS(10000."Request successful"),
    FAILED(10001."Operation failed"),
    TOKEN_FAILED(10002."Token failure"),

    NONE(99999."No");

    private int code;
    private String msg;

    private ResultCode(int code, String msg) {
        this.code = code;
        this.msg = msg; }... }Copy the code

Apply to our interface:

package com.cc.controller;

import com.cc.api.Result;
import com.cc.api.ResultCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/a")
public class TestController {
    @GetMapping("/t1")
    public Result t1(a) {
        return Result.success("Request successful, data returned");
    }

    @GetMapping("/t2")
    public Result t2(a) {
        return Result.failed("Request failed, this is error message.");
    }

    // Return a custom error
    @GetMapping("/t3")
    public Result t3(a) {
        returnResult.failed(ResultCode.TOKEN_FAILED); }}Copy the code

Return content:

{" code ": 10000," MSG ":" request is successful ", "data" : "request is successful, the return data", "success" : true}Copy the code

Use annotations to make the implementation more elegant

Although we have a preliminary implementation function, but now we need to change every interface return objects to the Result, there are two problems, one is the interface if we have a lot, so change the momentum is large, second, it is difficult to distinguish what kind of value, the interface should have returned it caused the maintenance is not clear, can’t stand it.

So keep the code of the original interface, and then add annotations to the interface class to unify what is returned.

We will create three classes:

  • ResponseResult, annotation class
  • ResponseResultAdvice, returns the result handling class
  • ResponseResultInterceptor, interceptors

ResponseResult:

package com.cc.response;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** * the annotation * that needs to encapsulate the return value uniformly@author cc
 * @dateIn 2021-07-12 he * /
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseResult {
}
Copy the code

ResponseResultAdvice:

package com.cc.response;

import com.cc.api.Result;
import com.cc.api.ResultCode;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/** * Unified encapsulation returns the result *@author cc
 * @dateThe 2021-07-12 10:53 * /
@ControllerAdvice
public class ResponseResultAdvice implements ResponseBodyAdvice<Object> {
    /** * Tag name *@author cc
     * @dateThe 2021-07-12 10:53 * /
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    /** * if the request has a wrapper tag, return it directly without overwriting the return body *@author cc
     * @dateThe 2021-07-12 10:53 * /
    @Override
    public boolean supports(MethodParameter methodParameter, Class
       > aClass) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        ResponseResult responseResult = (ResponseResult)request.getAttribute(RESPONSE_RESULT_ANN);
        // Whether to execute the beforeBodyWrite method
        returnresponseResult ! =null;
    }

    /** * The execution method of response processing *@author cc
     * @dateThe 2021-07-12 10:53 * /
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class
       > aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        / * * * no direct packing String because when the return value is a String, can use beforeBodyWrite StringHttpMessageConverter converter, * Cannot be cast to java.lang.String; Package String as Result at the interface layer, which needs to be agreed with the developers and is a hassle * 2. Om = new ObjectMapper(); ObjectMapper om = new ObjectMapper(); Return om. WriteValueAsString (Result. Success (o))) 3. On the WebMvcConfigurer configuration class, change the order of the converters so that Json is processed earlier than String (implemented in the WebMvcConfig class) */

        // If it is already the Result class, no more encapsulation is done
        if (o instanceof Result) {
            return o;
        }
        // If it is a list class
        if (o instanceof List) {
            // do something
        }
        // If it is an integer
        if (o instanceof Integer) {
            // do something
        }
        returnResult.success(o); }}Copy the code

ResponseResultInterceptor:

package com.cc.response;

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/** * returns result interceptor *@author cc
 * @dateSqlstate 2021-07-12 * /
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
    /** * Tag name *@author cc
     * @dateSqlstate 2021-07-12 * /
    public  static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod)handler;
            finalClass<? > clazz = handlerMethod.getBeanType();final Method method = handlerMethod.getMethod();
            // Determine if the annotation exists on the class
            if (clazz.isAnnotationPresent(ResponseResult.class)) {
                // Set the request body, add the tag, pass it down, and judge at ResponseResultAdvice
                request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
Println (" class has ResponseResult annotation "); // system.out.println (" class has ResponseResult annotation ");
            } else if(method.isAnnotationPresent(ResponseResult.class)) {
                request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
// system.out.println (" method has ResponseResult annotation ");}}return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}Copy the code

To make this work, we add it to the configuration class:

WebMvcConfig:

package com.cc.config;

import com.cc.response.ResponseResultInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

/** * Web MVC configuration class *@author cc
 * @dateThe 2021-07-12 10:29 * /
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    private final ResponseResultInterceptor responseResultInterceptor;

    public WebMvcConfig(ResponseResultInterceptor responseResultInterceptor) {
        this.responseResultInterceptor = responseResultInterceptor;
    }

    /** ** return Result *@author cc
     * @dateThe 2021-07-12 at 10:30 * /
    / / interface version management, add to on the requestMappingHandlerMapping filter code
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(responseResultInterceptor).addPathPatterns("/ * *");
        super.addInterceptors(registry);
    }

    @Override
    protected void configureMessageConverters(List
       
        > converters)
       > {
        // Set the execution order of the JSON processor ahead of time to avoid an error when Result is executed by the String processor
        converters.add(0.newMappingJackson2HttpMessageConverter()); }}Copy the code

Then add this annotation where necessary, without changing the original interface code:

package com.cc.controller;

import com.cc.response.ResponseResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@ResponseResult
@RestController
@RequestMapping("/b")
public class Test2Controller {
    @GetMapping("/t1")
    public String t1(a) {
        return "hello java";
    }

    @GetMapping("/t2")
    public Integer t2(a) {
        return 1; }}Copy the code

Return result:

{" code ": 10000," MSG ":" request is successful ", "data" : "hello Java", "success" : true}Copy the code

Combined with global exception catching

When an exception occurs, the Result Result will not take effect. Therefore, combined with global exception capture, the system can still return the standard Result to the front end when the program fails:

GlobalExceptionHandler:

package com.cc.exception;

import com.cc.api.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/** * global exception capture, return * as Result when an exception occurs@author cc
 * @date2021/06/16 he * /
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public Result<Object> catchException(Exception e) {
        todoErrorLogger(e, null);
        e.printStackTrace();
        return Result.failed(e.getMessage());
    }

    private void todoErrorLogger(Exception e, String customErrorMsg) {
        StringBuilder sb = new StringBuilder();
        sb.append(e.getClass().getName()).append(":");
        if(customErrorMsg ! =null) {
            sb.append(customErrorMsg);
        } else {
            sb.append(e.getMessage());
        }
        StackTraceElement[] elements = e.getStackTrace();
        if(elements.length > 0){
            StackTraceElement element = elements[0];
            sb.append("##function:").append(element.getClassName()).append("-").append(element.getMethodName()).append("-").append(element.getLineNumber()); } e.printStackTrace(); }}Copy the code