spring mvc 中存在2中响应,一个是数据响应(常用于API开发),另一种是页面响应(常用于单体项目的开发)。
JSON的响应
spring boot 中只要引入了 web 启动器,就会自动的引入json的相关功能。在接口上标注@ResponseBody即可。
返回值解析器,一共15中:
处理返回值:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { //寻找返回值处理器 HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } else { handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } }
跟参数处理器一样,返回值处理器也是先判断是否支持,然后在调用返回值处理器进行处理。
支持的返回值:
返回值处理,利用MessageConverters进行处理:
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
内容协商:
最终使用MappingJackJon2HttpMessageConverter,转换成了json中。注意一点的是MappingJackJon2HttpMessageConverter是可以支持所有的返回值类型。
内容协商就是客户端和服务端根据实际情况,约定双方需要的数据格式。也可能在某种情况下,相同的接口web端需要返回json而手机端需要返回xml,这个时候就可以用到spring mvc的内容协商。
根据客户端接收能力的不同,而返回不同媒体类型的数据。
也可以开启参数方式,参数中包含format=json\xml 就可以返回不同的模式。当然也可以自定义MediaType,也就是重写 MessageConverter
spring: mvc: contentnegotiation: favor-parameter: true #开启请求参数内容协商模式
接口端会根据 Accept 参数,进行判断返回格式
如果同时定义了,就会根据权重进行判断,
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
上面的q=0.9 就是权重,因为xml的权重0.9大于json的权重0.8,所以会有限返回xml。
内容协商的原理:
自定义MessageConverter
再次整理一下内容协商的流程:
自定义MessageConverter过程:
WebMvcConfigurationSupport,xml的Converter支持源码,发现系统中存在相关类,直接导入Converter:
在spring boot 配置spring mvc 相关内容,只有一个入口就是 WebMvcConfigurer,当使用者期望配置相关Converter的时候,直接向容器中注入@Bean:
@Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //添加自定义的Converter converters.add(new MgMessageConverter()); } }; }
自定义Converter:
package com.example.demo.converter; import com.example.demo.dto.Person; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import java.io.IOException; import java.util.List; /** * 自定义的Converter */ public class MgMessageConverter implements HttpMessageConverter<Person> { /** * 是否能够读取,按照自动以类型读 * @param clazz * @param mediaType * @return */ @Override public boolean canRead(Class<?> clazz, MediaType mediaType) { return false; } /** * 是否能读 * 是 person类型的就能读 * @param clazz * @param mediaType * @return */ @Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return clazz.isAssignableFrom(Person.class); } /** * 支持媒体类型,服务要知道这个Converter能够支持哪些媒体类型 * application/x-mg * @return */ @Override public List<MediaType> getSupportedMediaTypes() { return MediaType.parseMediaTypes("application/x-mg"); } @Override public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } /** * 数据写出 * @param person * @param contentType * @param outputMessage * @throws IOException * @throws HttpMessageNotWritableException */ @Override public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { String data = person.getName() + ";" + person.getSex() + ";" + person.getAge(); //数据写到流中 outputMessage.getBody().write(data.getBytes()); } }
客户端请求:
跟踪源码可以看到,获取客户端的媒体类型为:
服务器可以处理消息的Converter:
以上是使用请求头的方式,下面可以说一下使用参数方式。
因为参数协商策略只支持json和xml,所以就需要自定义策略:
自定义策略也十分的简单:
package com.example.demo.conf; import com.example.demo.converter.MgMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.stereotype.Component; import org.springframework.web.accept.ParameterContentNegotiationStrategy; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @Component @Configuration(proxyBeanMethods = false) public class WebMvcConfig { @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { /** * 自定义Converter * @param converters */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //添加自定义的Converter converters.add(new MgMessageConverter()); } /** * 自定义协商管理 * @param configurer */ @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { //支持的媒体类型 Map<String, MediaType> mediaTypes = new HashMap<>(); mediaTypes.put("json", MediaType.APPLICATION_JSON); mediaTypes.put("xml", MediaType.APPLICATION_ATOM_XML); mediaTypes.put("mg", MediaType.parseMediaType("application/x-mg")); ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(mediaTypes); configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy)); } }; } }
跟踪代码可以看到,已经从原来的2中支持到现在的3中了:
在自定义功能的时候,小心默认功能失效,这个时候需要dubeg源码,查看哪里出现问题,解决即可。