SpringBoot实战之实现结果的优雅响应案例详解

 更新时间:2021年09月03日 11:28:38   作者:沉潜飞动  
这篇文章主要介绍了SpringBoot实战之实现结果的优雅响应案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

今天说一下 Spring Boot 如何实现优雅的数据响应:统一的结果响应格式、简单的数据封装。

前提

无论系统规模大小,大部分 Spring Boot 项目是提供 Restful + json 接口,供前端或其他服务调用,格式统一规范,是程序猿彼此善待彼此的象征,也是减少联调挨骂的基本保障。

通常响应结果中需要包含业务状态码、响应描述、响应时间戳、响应内容,比如:

{
"code": 200,
"desc": "查询成功",
"timestamp": "2020-08-12 14:37:11",
"data": {
"uid": "1597242780874",
"name": "测试 1"
}
}

对于业务状态码分为两个派系:一个是推荐使用 HTTP 响应码作为接口业务返回;另一种是 HTTP 响应码全部返回 200,在响应体中通过单独的字段表示响应状态。两种方式各有优劣,个人推荐使用第二种,因为很多 Web 服务器对 HTTP 状态码有拦截处理功能,而且状态码数量有限,不够灵活。比如返回 200 表示接口处理成功且正常响应,现在需要有一个状态码表示接口处理成功且正常响应,但是请求数据状态不对,可以返回 2001 表示。

自定义响应体

定义一个数据响应体是返回统一响应格式的第一步,无论接口正常返回,还是发生异常,返回给调用方的结构格式都应该不变。给出一个示例:

@ApiModel
@Data
public class Response<T> {
    @ApiModelProperty(value = "返回码", example = "200")
    private Integer code;
    @ApiModelProperty(value = "返回码描述", example = "ok")
    private String desc;
    @ApiModelProperty(value = "响应时间戳", example = "2020-08-12 14:37:11")
    private Date timestamp = new Date();
    @ApiModelProperty(value = "返回结果")
    private T data;
}

这样,只要在 Controller 的方法返回Response就可以了,接口响应就一致了,但是这样会形成很多格式固定的代码模板,比如下面这种写法:

@RequestMapping("hello1")
public Response<String> hello1() {
    final Response<String> response = new Response<>();
    response.setCode(200);
    response.setDesc("返回成功");
    response.setData("Hello, World!");
    return response;
}

调用接口响应结果为:

{
"code": 200,
"desc": "返回成功",
"timestamp": "2020-08-12 14:37:11",

     "data": "Hello, World!"

}

这种重复且没有技术含量的代码,怎么能配得上程序猿这种优(lan)雅(duo)的生物呢?最好能在返回响应结果的前提下,减去那些重复的代码,比如:

@RequestMapping("hello2")
public String hello2() {
    return "Hello, World!";
}

这就需要借助 Spring 提供的ResponseBodyAdvice来实现了。

全局处理响应数据

先上代码:

/**
 * <br>created at 2020/8/12
 *
 * @author www.howardliu.cn
 * @since 1.0.0
 */
@RestControllerAdvice
public class ResultResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
        return !returnType.getGenericParameterType().equals(Response.class);// 1
    }

    @Override
    public Object beforeBodyWrite(final Object body, final MethodParameter returnType, final MediaType selectedContentType,
                                  final Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  final ServerHttpRequest request, final ServerHttpResponse response) {
        if (body == null || body instanceof Response) {
            return body;
        }
        final Response<Object> result = new Response<>();
        result.setCode(200);
        result.setDesc("查询成功");
        result.setData(body);
        if (returnType.getGenericParameterType().equals(String.class)) {// 2
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                return objectMapper.writeValueAsString(result);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("将 Response 对象序列化为 json 字符串时发生异常", e);
            }
        }
        return result;
    }
}

/**
 * <br>created at 2020/8/12
 *
 * @author www.howardliu.cn
 * @since 1.0.0
 */
@RestController
public class HelloWorldController {
    @RequestMapping("hello2")
    public String hello2() {
        return "Hello, World!";
    }

    @RequestMapping("user1")
    public User user1() {
        User u = new User();
        u.setUid(System.currentTimeMillis() + "");
        u.setName("测试1");
        return u;
    }
}

上面代码是实现了 Spring ResponseBodyAdvice类的模板方式,按照 Spring 的要求实现就行。只有两个需要特别注意的地方,也就是代码中标注 1 和 2 的地方。

首先说 1 这一行,也就是supports方法,这个方法是校验是否需要调用beforeBodyWrite方法的前置判断,返回true则执行beforeBodyWrite方法,这里根据 Controller 方法返回类型来判断是否需要执行beforeBodyWrite,也可以一律返回true,在后面判断是否需要进行类型转换。

然后重点说下 2 这一行,这行是坑,是大坑,如果对 Spring 结构不熟悉的,绝对会在这徘徊许久,不得妙法。

代码 2 这一行是判断Controller的方法是否返回的是String类型的结果,如果是,将返回的对象序列化之后返回。

这是因为SpringString类型的响应类型单独处理了,使用StringHttpMessageConverter类进行数据转换。在处理响应结果的时候,会在方法getContentLength中计算响应体大小,其父类方法定义是protected Long getContentLength(T t, @Nullable MediaType contentType),而StringHttpMessageConverter将方法重写为protected Long getContentLength(String str, @Nullable MediaType contentType),第一个参数是响应对象,固定写死是String类型,如果我们强制返回Response对象,就会报ClassCastException

当然,直接返回String的场景不多,这个坑可能会在某天特殊接口中突然出现。

补充说明

上面只是展示了ResponseBodyAdvice类最简单的应用,我们还可以实现更多的扩展使用。比如:

  1. 返回请求ID:这个需要与与RequestBodyAdvice联动,获取到请求ID后,在响应是放在响应体中;
  2. 结果数据加密:通过ResponseBodyAdvice实现响应数据加密,不会侵入业务代码,而且可以通过注解方式灵活处理接口的加密等级;
  3. 有选择的包装响应体:比如定义注解IgnoreResponseWrap,在不需要包装响应体的接口上定义,然后在supports方法上判断方法的注解即可,比如:
@Override
public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
    final IgnoreResponseWrap[] declaredAnnotationsByType = returnType.getExecutable().getDeclaredAnnotationsByType(IgnoreResponseWrap.class);
    return !(declaredAnnotationsByType.length > 0 || returnType.getGenericParameterType().equals(Response.class));
}

很多其他玩法就不一一列举了。

总结

上面说了正常响应的数据,只做到了一点优雅,想要完整,还需要考虑接口异常情况,总不能来个大大的try/catch/finally包住业务逻辑吧,那也太丑了。后面会再来一篇,重点说说接口如何在出现异常时,也能返回统一的结果响应。

本文只是抛出一块砖,玉还得自己去找。

推荐阅读

到此这篇关于SpringBoot实战之实现结果的优雅响应案例详解的文章就介绍到这了,更多相关SpringBoot实战之实现结果的优雅响应内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java深入探索线程安全和线程通信的特性

    Java深入探索线程安全和线程通信的特性

    这篇文章主要介绍了Java线程安全和线程通信的特性,线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况
    2022-05-05
  • springboot集成mybatisPlus+多数据源的实现示例

    springboot集成mybatisPlus+多数据源的实现示例

    这篇文章主要介绍了springboot集成mybatisPlus+多数据源的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • IntelliJ IDEA 热部署插件JRebel的使用

    IntelliJ IDEA 热部署插件JRebel的使用

    这篇文章主要介绍了IntelliJ IDEA 热部署插件JRebel的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • springboot排除某些自动配置的操作方法

    springboot排除某些自动配置的操作方法

    Spring Boot 提供的自动配置非常强大,某些情况下,自动配置的功能可能不符合我们的需求,需要我们自定义配置,这个时候就需要排除/禁用Spring Boot 某些类的自动化配置了,本文给大家介绍springboot排除某些自动配置的方法,感兴趣的朋友一起看看吧
    2023-08-08
  • java集成开发SpringBoot生成接口文档示例实现

    java集成开发SpringBoot生成接口文档示例实现

    这篇文章主要为大家介绍了java集成开发SpringBoot如何生成接口文档的示例实现过程,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-10-10
  • 详解Java对象的内存布局

    详解Java对象的内存布局

    这篇文章主要介绍了Java对象的内存布局,对对象内存感兴趣的同学,一定要仔细研究下
    2021-04-04
  • spring boot openfeign从此和httpClient说再见详析

    spring boot openfeign从此和httpClient说再见详析

    这篇文章主要给大家介绍了关于spring boot openfeign从此和httpClient说再见的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧
    2018-06-06
  • SpringBoot多环境配置方式的新手教程

    SpringBoot多环境配置方式的新手教程

    我们平时做项目的时候,一般都会分几套环境,每一套环境的配置都是不一样的,所以这篇文章就来为大家详细介绍一下SpringBoot多环境配置方式,希望对大家有所帮助
    2023-11-11
  • Java从内存角度带你理解数组名实质是个地址的论述

    Java从内存角度带你理解数组名实质是个地址的论述

    这篇文章主要介绍了Java如何从内存解析的角度理解“数组名实质是一个地址”,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-09-09
  • SpringMVC中文乱码踩坑记录

    SpringMVC中文乱码踩坑记录

    这篇文章主要介绍了SpringMVC中文乱码踩坑记录,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08

最新评论