美文网首页
SpringBoot系列教程之定义接口返回类型的几种方式

SpringBoot系列教程之定义接口返回类型的几种方式

作者: Kyriez7 | 来源:发表于2022-08-19 09:39 被阅读0次

    SpringBoot 系列教程之定义接口返回类型的几种方式

    实现一个 web 接口返回 json 数据,基本上是每一个 javaer 非常熟悉的事情了;那么问题来了,如果我有一个接口,除了希望返回 json 格式的数据之外,若也希望可以返回 xml 格式数据可行么?

    答案当然是可行的,接下来我们将介绍一下,一个接口的返回数据类型,可以怎么处理

    I. 项目搭建

    本文创建的实例工程采用 SpringBoot 2.2.1.RELEASE + maven 3.5.3 + idea 进行开发

    1. pom 依赖

    具体的 SpringBoot 项目工程创建就不赘述了,对于 pom 文件中,需要重点关注下面两个依赖类

    <dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>com.fasterxml.jackson.dataformat</groupId>        <artifactId>jackson-dataformat-xml</artifactId>    </dependency></dependencies>
    

    注意 jackson-datafromat-xml 这个依赖,加上这个主要时为了支持返回 xml 格式的数据

    II. 返回类型设置的多种方式

    正常来讲,一个 RestController 的接口,默认返回的是 Json 格式数据,当我们引入了上面的 xml 包之后,会怎样呢?返回的还是 json 么?

    1.通过 produce 设置返回类型

    如果一个接口希望返回 json 或者 xml 格式的数据,最容易想到的方式就是直接设置 RequestMapping 注解中的 produce 属性

    这个值主要就是用来设置这个接口响应头中的 content-type ; 如我们现在有两个接口,一个指定返回 json 格式数据,一个指定返回 xml 格式数据,可以如下写

    @RestControllerpublic class IndexRest{    @Data    public static class ResVo<T> {        private int code;        private String msg;        private T data;        public ResVo(int{            this.code = code;            this.msg = msg;            this.data = data;        }    }    @GetMapping(path = "/xml", produces = {MediaType.APPLICATION_XML_VALUE})    public ResVo<String> xml(){        return new ResVo<>(0, "ok", "返回xml");    }    @GetMapping(path = "/json", produces = {MediaType.APPLICATION_JSON_VALUE})    public ResVo<String> json(){        return new ResVo<>(0, "ok", "返回json");    }}
    

    上面的实现中

    produces = application/xmlproduces = applicatin/json
    

    接下来我们访问一下看看返回的是否和预期一致

    image.png

    从上面截图也可以看出,xml 接口返回的是 xml 格式数据;json 接口返回的是 json 格式数据

    2. 通过请求头 accept 设置返回类型

    上面的方式,非常直观,自然我们就会有一个疑问,当接口上不指定 produces 属性时,直接访问会怎么表现呢?

    @GetMapping(path = "/")public ResVo<String> index(){    return new ResVo<>(0, "ok", "简单的测试");}
    
    image.png

    请注意上面的截图,两种访问方式返回的数据类型不一致

    application/xhtml+xml
    

    那么问题来了,为什么两者的表现形式不一致呢?

    对着上面的图再看三秒,会发现主要的差别点就在于请求头 Accept 不同;我们可以通过这个请求头参数,来要求服务端返回我希望的数据类型

    如指定返回 json 格式数据

    curl 'http://127.0.0.1:8080' -H 'Accept:application/xml' -ivcurl 'http://127.0.0.1:8080' -H 'Accept:application/json'
    
    image.png

    从上面的执行结果也可以看出,返回的类型与预期的一致;

    说明

    请求头可以设置多种 MediaType,用英文逗号分割,后端接口会根据自己定义的 produce 与请求头希望的 mediaType 取交集,至于最终选择的顺序则以 accept 中出现的顺序为准

    看一下实际的表现来验证下上面的说法

    image.png

    通过请求头来控制返回数据类型的方式可以说是非常经典的策略了,(遵循 html 协议还有什么好说的呢!)

    3. 请求参数来控制返回类型

    除了上面介绍的两种方式之外,还可以考虑为所有的接口,增加一个根据特定的请求参数来控制返回的类型的方式

    比如我们现在定义,所有的接口可以选传一个参数 mediaType ,如果值为 xml,则返回 xml 格式数据;如果值为 json,则返回 json 格式数据

    当不传时,默认返回 json 格式数据

    基于此,我们主要借助 mvc 配置中的内容协商
    ContentNegotiationConfigurer 来实现

    @SpringBootApplicationpublic class Application implements WebMvcConfigurer{    @Override    public void configureContentNegotiation(ContentNegotiationConfigurer configurer){        configurer.favorParameter(true)                // 禁用accept协商方式,即不关心前端传的accept值//                .ignoreAcceptHeader(true)                // 哪个放在前面,哪个的优先级就高; 当上面这个accept未禁用时,若请求传的accept不能覆盖下面两种,则会出现406错误                .defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)                // 根据传参mediaType来决定返回样式                .parameterName("mediaType")                // 当acceptHeader未禁用时,accept的值与mediaType传参的值不一致时,以mediaType的传值为准                // mediaType值可以不传,为空也行,但是不能是json/xml之外的其他值                .mediaType("json", MediaType.APPLICATION_JSON)                .mediaType("xml", MediaType.APPLICATION_XML);    }    public static void main(String[] args){        SpringApplication.run(Application.class);    }}
    

    上面的实现中,添加了很多注释,先别急;我来逐一进行说明

    .parameterName("mediaType")// 当acceptHeader未禁用时,accept的值与mediaType传参的值不一致时,以mediaType的传值为准// mediaType值可以不传,为空也行,但是不能是json/xml之外的其他值.mediaType("json", MediaType.APPLICATION_JSON).mediaType("xml", MediaType.APPLICATION_XML);复制代码</pre>上面这三行代码,主要就是说,现在可以根据传参 mediaType 来控制返回的类型,我们新增一个接口来验证一下<pre class="prettyprint hljs dart">@GetMapping(path = "param")public ResVo<String> params(@RequestParam(name = "mediaType", required = false) String mediaType) {    return new ResVo<>(0, "ok", String.format("基于传参来决定返回类型:%s", mediaType));}
    

    我们来看下几个不同的传参表现

    # 返回json格式数据curl 'http://127.0.0.1:8080/param?mediaType=json' -iv# 返回xml格式数据curl 'http://127.0.0.1:8080/param?mediaType=xml' -iv# 406错误curl 'http://127.0.0.1:8080/param?mediaType=text' -iv# 走默认的返回类型,json在前,所以返回json格式数据(如果将xml调整到前面,则返回xml格式数据,主要取决于 `.defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)`)curl 'http://127.0.0.1:8080/param'
    
    image.png

    疑问:若请求头中传递了 Accept 或者接口上定义了 produce,会怎样?

    当指定了 accept 时,并且传参中指定了 mediaType,则以传参为准

    • 如 accept: application/json,application.xml , 此时 mediaType=json , 返回 json 格式
    • 如 accept: application/json , 此时 mediaTyep=xml , 返回 xml 格式
    • 如 accept: text/html ,此时 mediaType=xml ,此时返回的也是 xml 格式
    • 如 accept: text/html ,此时 mediaType 不传时 ,因为无法处理 text/html 类型,所以会出现 406
    • 如 accept: application/xml , 但是 mediaType 不传,虽然默认优先是 json,此时返回的也是 xml 格式,与请求头希望的保持一致

    但是若传参与 produce 冲突了,那么就直接 406 异常,不会选择 mediaType 设置的类型

    • 如 produce = applicatin/json , 但是 mediaType=xml ,此时就会喜提 406

    细心的小伙伴可能发现了上面的配置中,注释了一行 .ignoreAcceptHeader(true) ,当我们把它打开之后,前面说的 Accept 请求头可以随意传,我们完全不 care,当做没有传这个参数进行处理即可开

    4.小结

    本文介绍了三种方式,控制接口返回数据类型

    方式一

    接口上定义 produce, 如 @GetMapping(path = "p2", produces = {"application/xml", "application/json"})

    注意 produces 属性值是有序的,即先定义的优先级更高;当一个请求可以同时接受 xml/json 格式数据时,上面这个定义会确保这个接口现有返回 xml 格式数据

    方式二

    借助标准的请求头 accept,控制希望返回的数据类型;但是需要注意的时,使用这种方式时,要求后端不能设置
    ContentNegotiationConfigurer.ignoreAcceptHeader(true)

    在实际使用这种方式的时候,客户端需要额外注意,Accept 请求头中定义的 MediaType 的顺序,是优于后端定义的 produces 顺序的,因此用户需要将自己实际希望接受的数据类型放在前面,或者干脆就只设置一个

    方式三

    借助
    ContentNegotiationConfigurer 实现通过请求参数来决定返回类型,常见的配置方式形如

    configurer.favorParameter(true)        // 禁用accept协商方式,即不关心前端传的accept值      //                .ignoreAcceptHeader(true)        // 哪个放在前面,哪个的优先级就高; 当上面这个accept未禁用时,若请求传的accept不能覆盖下面两种,则会出现406错误        .defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)        // 根据传参mediaType来决定返回样式        .parameterName("mediaType")        // 当acceptHeader未禁用时,accept的值与mediaType传参的值不一致时,以mediaType的传值为准        // mediaType值可以不传,为空也行,但是不能是json/xml之外的其他值        .mediaType("json", MediaType.APPLICATION_JSON)        .mediaType("xml", MediaType.APPLICATION_XML);
    

    即添加这个设置之后,最终的表现为:

    produce
    

    注意注意:当配置中忽略了 AcceptHeader 时, .ignoreAcceptHeader(true) ,上面第三条作废

    相关文章

      网友评论

          本文标题:SpringBoot系列教程之定义接口返回类型的几种方式

          本文链接:https://www.haomeiwen.com/subject/nxaygrtx.html