项目“奇怪”问题丨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.class, args);
}
@RequestMapping("/hello")
@ResponseBody
public User test1(){
return new User(1,"lisi");
}
}
三. 异常重现
1.开启日志功能
代码编写完毕之后,我们可以先打开springboot的debug日志功能,如下所示:
debug=true
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.异常原因分析
我们可以观察控制台的后端日志内容,如下:
在控制台中可以发现关键信息如下:
# 最终的仲裁结果,响应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值。
这里我们可以看到在响应头中,ContentType就是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");
}
这时再进行接口请求,就可以看到如下格式的响应信息了。
此时在控制台中的日志内容如下:
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资料免费学!