[Spring Boot] Actual Combat: Realizing Elegant Data Return

Practice: Realize elegant data return

This section describes how to allow the frontend and backend to interact with data gracefully, how to unify the data format for normal data, and how to handle exceptions in a unified manner and return data in a unified format.

1. Why should the return value be unified

In the process of project development, data interaction issues under the system architecture of server and client interface data transmission or separation of front and back are often involved. How to ensure that the data is complete, clear and easy to understand is a big challenge for developers. Defining a unified data return format is conducive to improving development efficiency, reducing communication costs, and reducing the development cost of the caller. Currently more popular is data interaction based on JSON format. But JSON is only the format of the message, and the data content in it needs to be redesigned and defined. Regardless of whether it is an HTTP interface or an RPC interface, it is very important to maintain a uniform return value format.

In the project, we will encapsulate the response into JSON and return it. Generally, the data format of all interfaces will be unified, so that the front-end (iOS, Android, Web) operates on the data consistently and easily. Under normal circumstances, there is no fixed specification for the unified return data format, as long as the returned data status and the specific data to be returned can be clearly described, but it generally includes three parts: status code, message prompt, and specific data. For example, the basic data format returned by the general system requirements is as follows:

	{
    
    
		"code":20000,
		"message": "成功",
		"data": {
    
    
			"item": [
				{
    
    
					"id":"1",
					"name":"ysxq",
					"intro":"备注"
				}
			]
		}
	}

From the above example, we know that the defined return value contains 4 elements: response result, response code, message, and return data.

2. Unified data return

2.1 Define data format

Define the basic elements of the return value to ensure that the background execution returns these fields regardless of success or failure, and no other fields appear. The defined return value includes the following:

  • Integer code: Returns 0 on success, and returns a specific error code on failure.
  • String message: returns null on success, and returns a specific error message on failure.
  • T data: returns a specific value on success, and null on failure.

According to the definition of the returned data format above, the actual returned data template is as follows:

	{
    
    
		"code":20000,
		"message": "成功",
		"data": {
    
    
			"item": [
				{
    
    
					"id":"1",
					"name":"ysxq",
					"intro":"备注"
				}
			]
		}
	}

Among them, the data field is a generic field, which returns the data type required by the front end according to the actual business.

2.2 Define status code

There is a very important field in the returned data: status code. The status code field allows the server and the client to clearly know the result of the operation, whether the business is successfully processed, and if it fails, the reason for the failure and other information. Therefore, it is very important to define clear and understandable status codes. Status codes are defined as shown in the table.

insert image description here
The above definition is a general status code, and other business-related status codes need to be defined according to the actual business.

2.3 Define data processing class

The format of the returned data and the status code of the processing result are defined above, and the general result processing class is defined next. It can be handled according to the situation in actual use. The simple definition in this example is as follows:

/**
 * @Title: JSONResult.java
 * @Package com.example.demo
 * @Description: 自定义响应数据结构
                200:表示成功
                500:表示错误,错误信息在msg字段中
                501: bean验证错误,无论多少个错误都以map形式返回
                502:拦截器拦截到用户token出错
                555:异常抛出信息
*/
public class JSONResult {
    
    
    //定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();
    //响应业务状态
    private Integer status;
    //响应消息
    private String msg;
    //响应中的数据
    private Object data;

    public static JSONResult build(Integer status, String msg, Object data) {
    
    
        return new JSONResult(status, msg, data);
    }
    public static JSONResult ok(Object data) {
    
    
        return new JSONResult(data);
    }
    public static JSONResult ok() {
    
    
        return new JSONResult(null);
    }
    public static JSONResult errorMsg(String msg) {
    
    
        return new JSONResult(500, msg, null);
    }
    public static JSONResult errorMap(Object data) {
    
    
        return new JSONResult(501, "error", data);
    }
    public static JSONResult errorTokenMsg(String msg) {
    
    
        return new JSONResult(502, msg, null);
    }
    public static JSONResult errorException(String msg) {
    
    
        return new JSONResult(555, msg, null);
    }

    public JSONResult() {
    
    

    }

    public JSONResult (Integer status, String msg, Object data) {
    
    
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public JSONResult(Object data) {
    
    
        this.status = 200;
        this.msg = "OK";
        this.data = data;
    }
    public Boolean isOK() {
    
    
        return this.status == 200;
    }

/**
 * @Description: 将json结果集转化为JSONResult对象,需要转换的对象是一个类
 * @param jsonData
 * @param clazz
 * @return
*/
    public static JSONResult formatToPojo(String jsonData, Class<?> clazz) {
    
    
        try {
    
    
            if (clazz == null) {
    
    
                return MAPPER.readValue(jsonData, JSONResult.class);
            }
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (clazz != null) {
    
    
                if (data.isObject()) {
    
    
                    obj = MAPPER.readValue(data.traverse(), clazz);
                }else if (data.isTextual()) {
    
    
                    obj = MAPPER.readValue(data.asText(), clazz);
                }
            }
            return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
        } catch (Exception e) {
    
    
            return null;
        }
    }

/**
 *
 * @Description: 没有object对象的转换
 * @param json
 * @return
*/

public static JSONResult format(String json) {
    
    
    try {
    
    
        return MAPPER.readValue(json, JSONResult.class);
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return null;
}

/**
 *
 * @Description: Object是集合转换,需要转换的对象是一个list
 * @param jsonData
 * @param clazz
 * @return
 */
public static JSONResult formatToList(String jsonData, Class<?> clazz) {
    
    
    try {
    
    
        JsonNode jsonNode = MAPPER.readTree(jsonData);
        JsonNode data = jsonNode.get("data");
        Object obj = null;
        if (data.isArray() && data.size() > 0) {
    
    
            obj = MAPPER.readValue(data.traverse(), MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
        }
        return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
    } catch (Exception e) {
    
    
        return null;
    }
}
    public String getOK() {
    
    
        return ok;
    }
    public void setOK(String ok) {
    
    
        this.ok = ok;
    }

}

The data return processing class is defined above, and the response data structure is defined. The data return of all interfaces is uniformly processed through this type of processing. After receiving such data, it needs to use the method of this class to convert it into the corresponding data type format (class or list).

2.4 Processing data return

After defining the data processing class, add data processing to the returned data in the controller. The call is as follows:

 	@RequestMapping("/getUser")
    public JSONResult getUserJson() {
    
    
        User u = new User();
        u.setName( "ysxq");
        u.setAge(20);
        u.setBirthday(new Date());
        u.setPassword("ysxq123456");
        return JSONResult.ok(u);
    }

2.5 Testing

Start the helloworld project, visit it in the browser http://localhost:8080/user/getUser, and the page data returns as follows:

	{
    
    
		"code":200,
		"message": "OK",
		"data": {
    
    
			"name":"ysxq",
			"age": 20,
			"birthday": "2000-12-18 18:17:15"
		}
	}

The returned result data can be returned according to our expected result format under normal conditions.

3. Global exception handling

In the process of project development, abnormal situations will definitely be encountered. How to deal with abnormal situations, and how to ensure that the program can correctly return data when abnormal situations occur? Can't all methods be added with try catch? Next, we will introduce how Spring Boot handles global exceptions, and how to return data in a unified format after catching exceptions.

3.1 Implementation of global exception handling

Before the introduction, we need to understand the common exception handling methods in Spring. In general, there are many ways to handle exceptions in the Spring Boot framework. In terms of scope, they include global exception capture and local exception capture. Here are three more commonly used exception handling solutions.

(1) Use @ExceptionHandler to handle local exceptions

Exception handling is implemented in the controller by adding the method annotated with @ExceptionHandler. This method is very easy to implement, but it can only handle controller exceptions annotated with @ExceptionHandler, but cannot handle exceptions from other controllers, so it is not recommended.

(2) Configure the SimpleMappingExceptionResolver class to handle exceptions

The handling of global exceptions is realized by configuring the SimpleMappingExceptionResolver class, but this method cannot be used for special handling of specific exceptions, and all exceptions are handled in a unified manner.

(3) Use the ControllerAdvice annotation to handle global exceptions

Use @ControllerAdvice and @ExceptionHandler annotations to implement global exception handling. @ControllerAdvice defines the global exception handling class, and @ExceptionHandler specifies the exception type intercepted by the custom error handling method. Implement global exception capture and perform special handling for specific exceptions.

The above three solutions can achieve global exception handling. However, it is recommended to use the @ControllerAdvice annotation to handle global exceptions, so that different exceptions can be handled separately.

3.2 Use the @ControllerAdvice annotation to implement global exception handling

The following example demonstrates the @ControllerAdvice annotation to achieve global unified exception handling. Define a custom exception handling class GlobalExceptionHandler, the specific sample code is as follows:

@ControllerAdvice
public class GlobalExceptionHandler {
    
    
    public static final String ERROR_VIEW = "error";
    Logger logger = LoggerFactory.getLogger(getClass());
    @ExceptionHandler(value = {
    
    Exception.class})
    public Object errorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception {
    
    
        //e.printStackTrace();
        //记录日志
        logger.error(ExceptionUtils.getMessage(e));
        //是否Ajax请求
        if (isAjax(request)) {
    
    
            return JSONResult.errorException(e.getMessage());
        } else {
    
    
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", request.getRequestURL());
            mav.setViewName(ERROR_VIEW);
            return mav;
        }
    }
    /**
     * @Title: GlobalExceptionHandler.java
     * @Package com.example.demo
     * @Description: 判断是否是Aiax请求
     */
    public static boolean isAjax(HttpServletRequest httprequest) {
    
    
        return (httprequest.getHeader("X-Requested-With") !=null && "XMLHttpRequest".equals(httprequest.getHeader("X-Requested-With")));
    }
}

The above example handles all exceptions. If you need to handle other exceptions, such as NullPointerException, you only need to use the @ExceptionHandler(value = {NullPointerException.class}) annotation in the GlobalException class to redefine an exception handling method.

Start the project, enter http://localhost:8088/err/error in the browser, and the result is shown in the figure.
insert image description here

After the exception is handled, the page is automatically adjusted to a unified error page. If an Ajax request error occurs, the data will be returned uniformly in accordance with the defined JSON data format.

Guess you like

Origin blog.csdn.net/weixin_45627039/article/details/131964634