vlambda博客
学习文章列表

项目“奇怪”问题丨Springboot响应结果仲裁机制怎么解决?!

点击上方蓝字关注我们

一. 异常描述

二. 案例代码

这个问题其实也是Springboot面试时的常见问题,即Springboot对于响应结果的“仲裁机制”。

为了可以让大家能够详细的了解这个异常的产生原因,九哥给大家编写一个简单的案例来还原一下上述问题,并探讨Springboot响应结果的仲裁机制是怎么回事。

1.pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.6</version>
        <relativePath/> 
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo1</name>
    
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.User类

package com.example.demo1;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
}

3.Demo1Application类

package com.example.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@SpringBootApplication
@Controller
public class Demo1Application {

    public static void main(String[] args) {

        SpringApplication.run(Demo1Application.classargs);
    }

    @RequestMapping("/hello")
    @ResponseBody
    public User test1(){
        return new User(1,"lisi");
    }

}

三. 异常重现

1.开启日志功能

代码编写完毕之后,我们可以先打开springboot的debug日志功能,如下所示:

debug=true

项目“奇怪”问题丨Springboot响应结果仲裁机制怎么解决?!

2022-04-03 10:10:56.564 DEBUG 15072 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet        : GET "/hello", parameters={}
2022-04-03 10:10:56.564 DEBUG 15072 --- [nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo1.Demo1Application#test1()
2022-04-03 10:10:56.565 DEBUG 15072 --- [nio-8080-exec-8] m.m.a.RequestResponseBodyMethodProcessor : Using 'application/xhtml+xml', given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]
2022-04-03 10:10:56.565 DEBUG 15072 --- [nio-8080-exec-8] m.m.a.RequestResponseBodyMethodProcessor : Writing [User(id=1, name=lisi)]
2022-04-03 10:10:56.565 DEBUG 15072 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

3.异常原因分析

我们可以观察控制台的后端日志内容,如下:

项目“奇怪”问题丨Springboot响应结果仲裁机制怎么解决?!

在控制台中可以发现关键信息如下:

# 最终的仲裁结果,响应xml数据
Using 'application/xhtml+xml'
# 浏览器支持的响应格式
given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] 
# springboot可以支持的响应格式
and supported [application/json, application/*+json, application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]

通过日志,我们可以发现,SpringBoot(其实是SpringMVC框架)在处理本次请求时,分析了浏览器端可以接受的响应结果类型,即

[text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, /;q=0.8]

而Spring可以支持的响应结果类型为:

[application/json, application/+json, application/json, application/+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/+xml;charset=UTF-8, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/+xml;charset=UTF-8]

最终,SpringBoot根据以上信息,按照如下格式进行响应结果的类型仲裁:Using 'application/xhtml+xml'

4.仲裁机制原理

这里仲裁机制的执行逻辑是:

响应结果类型浏览器要接受,并且SpringBoot也要支持!当有多个响应类型都能够满足条件,那么优先选择q值高的类型q值的默认值为1

上面的demo中,text/html和 application/xhtml+xml等的q值都是1,而 /(任意类型)的q值为0.9。

因此,SpringBoot虽然也支持application/json这种响应格式,但是按照q值匹配,本次响应使用了q值更高的application/xhtml+xml作为响应格式。

有的小伙伴可能有疑问,浏览器是如何传递自己需要哪种响应格式的呢?如下图:Accept请求头中指定了浏览器支持的响应数据格式,并且设置了q值。

项目“奇怪”问题丨Springboot响应结果仲裁机制怎么解决?!

这里我们可以看到在响应头中,ContentType就是SpringBoot最终选择的响应数据格式。

项目“奇怪”问题丨Springboot响应结果仲裁机制怎么解决?!

四. 异常解决

以上就是这个同学遇到的“奇怪”问题原因分析过程。最后九哥给大家简单总结一下:由于项目的pom文件里引入了jackson-dataformat-xml依赖,使得SpringBoot可以响应xml格式数据,并且浏览器Accept头部中xml格式的q值比json格式(包含在/中)要高,因此,SpringBoot的仲裁结果为使用了xml格式进行响应。

那么如果我们希望这个接口只能返回json类型格式的数据,又该如何处理呢?

1.后端解决--输出时给出限定的输出格式

其实要想实现这种效果,在SpringBoot中是非常简单的,我们可以在后端接口中明确声明该接口的返回数据类型,代码如下:

    @RequestMapping(value = "/hello",produces = "application/json")
    @ResponseBody
    public User test1(){
        return new User(1,"lisi");
    }

这时再进行接口请求,就可以看到如下格式的响应信息了。

项目“奇怪”问题丨Springboot响应结果仲裁机制怎么解决?!

此时在控制台中的日志内容如下:

2022-04-03 11:03:35.392 DEBUG 7540 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet        : GET "/hello", parameters={}
2022-04-03 11:03:35.397 DEBUG 7540 --- [nio-8080-exec-4] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo1.Demo1Application#test1()
2022-04-03 11:03:35.407 DEBUG 7540 --- [nio-8080-exec-4] m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json;q=0.8', given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json]
2022-04-03 11:03:35.428 DEBUG 7540 --- [nio-8080-exec-4] m.m.a.RequestResponseBodyMethodProcessor : Writing [User(id=1, name=lisi)]
2022-04-03 11:03:35.438 DEBUG 7540 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

2.前端解决--请求时明确想要的输出格式

另外我们其实也可以在前端发起request请求时,由请求发送方明确指定自己想要接受的响应类型,例如通过发送ajax请求,可以指定接受服务器端返回的json类型数据。

var xhr = new XMLHttpRequest();
xhr.open('get','/hello');
xhr.setRequestHeader('Accept','application/json');
xhr.send();

此时同样可以得到json格式的响应信息。

以后如果你也遇到了同样的问题,知道该怎么解决了吧?如果你还有其他问题,可以给九哥留言哦。

今日份干货学习,喜欢的同学评论区回复“666”!

精选第一名,可以领取办公鼠标垫一个哦!

点击【阅读原文】,全套入门Java资料免费学!