SpringREST
基本概念
REST= RESTful = Representational State Transfer,is one way of providing interoperability between computer systems on the Internet
https://en.wikipedia.org/wiki/Representational_state_transfer
SpringMVC
Rest 在 springMVC 中使用
SpringMVC 使用 REST
实体类
public class Person { private Long id; private String name; // get&set }RestController
@RestController public class PersonRestController { @GetMapping("/person/{id}") public Person person(@PathVariable Long id, @RequestParam(required = false) String name) { Person person = new Person(); person.setId(id); person.setName(name); return person; } }浏览器访问

可以看到当前 content-type 类型是 application/json

修改媒体类型
springmvc 中有一个 @EnableWebMvc 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
上面有一个 DelegatingWebMvcConfiguration 的配置类

它继承自 WebMvcConfigurationSupport,在这个类里面加载各种类型

其中有一个 getDefaultMediaTypes 的方法

这个方法里面保存了可以使用的类型,如果要使用 xml,需要 jaxb2Present || jackson2XmlPresent,jackson2XmlPresent 根据静态代码块中判断,需要 com.fasterxml.jackson.dataformat.xml.XmlMapper

在 pom.xml 中加入 jackso-dataformat-xml 的依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.9</version>
</dependency>
然后重新访问浏览器,发现响应信息变成了 xml 格式


ACCEPT 说明:
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
q 表示权重,从左到右权重依次降低
第一优先顺序:text/html -> application/xhtml+xml -> application/xml
第二优先顺序:image/webp -> image/apng
第三优先顺序:signed-exchange
默认媒体类型源码分析
默认媒体类型是 application/json
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerAdapter->getMessageConverters)->addDefaultHttpMessageConverters->MappingJackson2HttpMessageConverter

所有的 HTTP 自描述消息处理器均在 messageConverters(类型:HttpMessageConverter),这个集合会传递到 RequestMappingHandlerAdapter,最终控制写出。
messageConverters,其中包含很多自描述消息类型的处理,比如 JSON、XML、TEXT等等
以 application/json 为例,Spring Boot 中默认使用 Jackson2 序列化方式,其中媒体类型:application/json,它的处理类 MappingJackson2HttpMessageConverter,提供两类方法:
- 读read* :通过 HTTP 请求内容转化成对应的 Bean
- 写write*: 通过 Bean 序列化成对应文本内容作为响应内容
媒体类型问题
问题:为什么第一次是JSON,后来增加了了 XML 依赖,又变成了 XML 内容输出?
回答:Spring Boot 应用默认没有增加 XML 处理器(HttpMessageConverter)实现,所以最后采用轮训的方式去逐一尝试是否可以 canWrite(POJO) ,如果返回 true,说明可以序列化该 POJO 对象,那么 Jackson2 恰好能处理,那么Jackson 输出了。
WebMvcConfiguration 中配置了 MessageConverter

这些配置来自于 addDefaultHttpMessageConverters 方法

每个messageConverter都要判断 canWrite,最后得到结果

这里的结果再和 acceptTypes 判断,当前 accept = "*/*"

然后根据权重进行排序,上图中
如果 selectedMediaType!=null,需要根据 messageConverters 重新判断 canWrite,并将最终结果write输出

selectedMediaType 来源,先判断 contentType,然后判断 MediaType,当前为 application/json

- 所以最终以 application/json 的方式进行输出
问题:当 Accept 请求头未被指定时,为什么还是 JSON 来处理?
回答:这个依赖于 messageConverters 的插入顺序。
问题:优先级是默认的是吧 可以修改吗?
回答:是可以调整的,通过 extendMessageConverters 方法调整
@Configuration public class WebMvcConfig implements WebMvcConfigurer { public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { System.err.println("converters: " + converters); // 扩展 messageConverter,在后面添加一个jackson converters.add(new MappingJackson2HttpMessageConverter()); // 扩展 messageConverter,在首位添加一个jackson converters.set(0, new MappingJackson2HttpMessageConverter()); } }
扩展媒体类型
Person
JSON 格式(application/json)
{
"id":1,
"name":"zhagnsan"
}
XML 格式(application/xml)
<Person>
<id>1</id>
<name>zhagnsan</name>
</Person>
Properties 格式(application/properties+person)
(需要扩展)
person.id = 1
person.name = zhagnsan
步骤:
实现 AbstractHttpMessageConverter 抽象类
- supports 方法:是否支持当前POJO类型
- readInternal 方法:读取 HTTP 请求中的内容,并且转化成相应的POJO对象(通过 Properties 内容转化成 JSON)
- writeInternal 方法:将 POJO 的内容序列化成文本内容(Properties格式),最终输出到 HTTP 响应中(通过 JSON 内容转化成 Properties )
public class PropertiesPersonHttpMessageConverter extends AbstractHttpMessageConverter<Person> { public PropertiesPersonHttpMessageConverter() { super(MediaType.valueOf("application/properties+person")); setDefaultCharset(StandardCharsets.UTF_8); } @Override protected boolean supports(Class<?> clazz) { return clazz.isAssignableFrom(Person.class); } /** * 讲请求内容中 Properties 内容转化成 Person 对象 * * @param clazz Class * @param inputMessage 输入消息 * @return * @throws IOException * @throws HttpMessageNotReadableException */ @Override protected Person readInternal(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { Person person = new Person(); InputStream inputStream = inputMessage.getBody(); Properties props = new Properties(); // 将请求中的内容转化成Properties props.load(new InputStreamReader(inputStream, getDefaultCharset())); // 将properties 内容转化到 Person 对象字段中 person.setId(Long.valueOf(props.getProperty("person.id"))); person.setName(props.getProperty("person.name")); return person; } /** * 响应信息中的Person对象转化成 Properties * * @param person Person * @param outputMessage 输出消息 * @throws IOException * @throws HttpMessageNotWritableException */ @Override protected void writeInternal(Person person, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream outputStream = outputMessage.getBody(); Properties props = new Properties(); props.setProperty("person.id", String.valueOf(person.getId())); props.setProperty("person.name", person.getName()); props.store(new OutputStreamWriter(outputStream, getDefaultCharset()), "Written by web server"); } }启用自定义 MessageConverter
@Configuration public class WebMvcConfig implements WebMvcConfigurer { public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // 扩展 MessageConverter,添加 PropertiesPersonHttpMessageConverter converters.add(new PropertiesPersonHttpMessageConverter()); } }使用 rest 请求指定 mediaType
- @RequestMappng 中的 consumes 对应 请求头 “Content-Type”
- @RequestMappng 中的 produces 对应 请求头 “Accept”
@PostMapping(value = "/person/json/to/properties", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = "application/properties+person") public Person personJsonToProperties(@RequestBody Person person) { // 请求的内容是 JSON // 响应的内容是 Properties return person; } @PostMapping(value = "/person/properties/to/json", consumes = "application/properties+person", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Person personPropertiesToJson(@RequestBody Person person) { // 请求的内容是 Properties // 响应的内容是 JSON return person; }使用 postman 测试
请求类型是 application/json;charset=UTF-8,响应类型是 application/properties+person

请求类型是 application/properties+person,响应类型是 application/json;charset=UTF-8

HttpMessageConverter 执行逻辑:
- 读操作:尝试是否能读取,canRead 方法去尝试,如果返回 true 下一步执行 read
- 写操作:尝试是否能写入,canWrite 方法去尝试,如果返回 true 下一步执行 write