【Spring Boot】实战:实现优雅的数据返回

实战:实现优雅的数据返回

本节介绍如何让前后台优雅地进行数据交互,正常的数据如何统一数据格式,以及异常情况如何统一处理并返回统一格式的数据。

1.为什么要统一返回值

在项目开发过程中经常会涉及服务端、客户端接口数据传输或前后台分离的系统架构下的数据交互问题。如何确保数据完整、清晰易懂是考验开发者的大难题。定义统一的数据返回格式有利于提高开发效率、降低沟通成本,降低调用方的开发成本。目前比较流行的是基于JSON格式的数据交互。但是JSON只是消息的格式,其中的数据内容还需要重新设计和定义。无论是HTTP接口还是RPC接口,保持返回值格式统一很重要。

在项目中,我们会将响应封装成JSON返回,一般会统一所有接口的数据格式,使前端(iOS、Android、Web)对数据的操作一致、轻松。一般情况下,统一返回数据格式没有固定的规范,只要能描述清楚返回的数据状态以及要返回的具体数据即可,但是一般会包含状态码、消息提示语、具体数据这3部分内容。例如,一般的系统要求返回的基本数据格式如下:

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

通过上面的示例我们知道,定义的返回值包含4要素:响应结果、响应码、消息、返回数据。

2.统一数据返回

2.1 定义数据格式

定义返回值的基本要素,确保后台执行无论成功还是失败都是返回这些字段,而不会出现其他的字段。定义的返回值包含如下内容:

  • Integer code:成功时返回0,失败时返回具体错误码。
  • String message:成功时返回null,失败时返回具体错误消息。
  • T data:成功时返回具体值,失败时为null。

根据上面的返回数据格式的定义,实际返回的数据模板如下:

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

其中,data字段为泛型字段,根据实际的业务返回前端需要的数据类型。

2.2 定义状态码

返回的数据中有一个非常重要的字段:状态码。状态码字段能够让服务端、客户端清楚知道操作的结果、业务是否处理成功,如果失败,失败的原因等信息。所以,定义清晰易懂的状态码非常重要。状态码定义如表所示。

在这里插入图片描述
以上定义的是通用状态码,其他的业务相关状态码需要根据实际业务定义。

2.3 定义数据处理类

前面定义了返回数据的格式和处理结果的状态码,接下来定义通用的结果处理类。在实际使用时可以根据情况处理。本示例中简单定义如下:

/**
 * @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;
    }

}

上面定义了数据返回处理类,定义了响应数据结构,所有接口的数据返回统一通过此类处理,接收此类数据后,需要使用本类的方法转换成对应的数据类型格式(类或者list)。

扫描二维码关注公众号,回复: 16030662 查看本文章

2.4 处理数据返回

定义数据处理类后,在控制器中将返回的数据统一加上数据处理。调用如下:

 	@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 测试

启动helloworld项目,浏览器中访问http://localhost:8080/user/getUser,页面数据返回如下:

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

返回的结果数据在正常的时候能够按照我们的预期结果格式返回。

3.全局异常处理

在项目开发的过程中肯定会碰到异常的情况,出现异常情况时如何处理,如何确保出现异常时程序也能正确地返回数据?总不能所有的方法都加上try catch吧?接下来介绍Spring Boot如何进行全局异常处理,捕获异常后如何按照统一格式返回数据。

3.1 全局异常处理的实现方式

在介绍之前,我们需要先了解Spring中常见的异常处理方式有哪些。一般Spring Boot框架的异常处理有多种方式,从范围来说,包括全局异常捕获处理方式和局部异常捕获处理方式。下面介绍3种比较常用的异常处理解决方案。

(1)使用@ExceptionHandler处理局部异常

在控制器中通过加入@ExceptionHandler注解的方法来实现异常的处理。这种方式非常容易实现,但是只能处理使用@ExceptionHandler注解方法的控制器异常,而无法处理其他控制器的异常,所以不推荐使用。

(2)配置SimpleMappingExceptionResolver类来处理异常

通过配置SimpleMappingExceptionResolver类实现全局异常的处理,但是这种方式不能针对特定的异常进行特殊处理,所有的异常都按照统一的方式处理。

(3)使用ControllerAdvice注解处理全局异常

使用@ControllerAdvice、@ExceptionHandler注解实现全局异常处理,@ControllerAdvice定义全局异常处理类,@ExceptionHandler指定自定义错误处理方法拦截的异常类型。实现全局异常捕获,并针对特定的异常进行特殊处理。

以上三种解决方案,都能实现全局异常处理。但是,推荐使用@ControllerAdvice注解方式处理全局异常,这样可以针对不同的异常分开处理。

3.2 使用@ControllerAdvice注解实现全局异常处理

下面通过示例演示@ControllerAdvice注解实现全局统一异常处理。定义一个自定义的异常处理类GlobalExceptionHandler,具体示例代码如下:

@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")));
    }
}

上面的示例,处理全部Exception的异常,如果需要处理其他异常,例如NullPointerException异常,则只需要在GlobalException类中使用@ExceptionHandler(value = {NullPointerException.class})注解重新定义一个异常处理的方法即可。

启动项目,在浏览器中输入http://localhost:8088/err/error,结果如图所示。
在这里插入图片描述

处理异常之后页面自动调整到统一的错误页面,如果是Ajax请求出错,则会按照定义的JSON数据格式统一返回数据。

猜你喜欢

转载自blog.csdn.net/weixin_45627039/article/details/131964634
今日推荐