1、RequestMappingHandlerAdapter.class
返回值处理器
执行目标方法
2、ServletInvocableHandlerMethod.class
确定参数,真正执行目标方法
的地方,有方法的返回值。
3、InvocableHandlerMethod.class
获取当前目标方法所有参数的属性值
通过反射
执行目标方法(也就是Controller方法)
4、UserController.java
进入目标方法
5、ServletInvocableHandlerMethod.class
利用返回值处理器处理返回值,把对象转换为json
6、HandlerMethodReturnValueHandlerComposite.class
找到支持的返回值处理器
挨个判断所有返回值处理器哪个支持处理目标方法的返回值
找到支持的返回值处理器调用 handleReturnValue 方法进行处理
7、RequestResponseBodyMethodProcessor.class
RequestResponseBodyMethodProcessor可以处理返回值标了@ResponseBody注解的,利用MessageConverters进行处理将数据写为json
8、AbstractMessageConverterMethodProcessor.class
内容协商
(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
注:自定义的格式不会出现在请求头里面,而是要通过基于请求参数的策略
服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁可以将对象写为json
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例:Book对象转为JSON。或者 JSON转为Book
将数据写出去
9、AbstractGenericHttpMessageConverter.class
将数据写给响应
10、AbstractJackson2HttpMessageConverter.class
将对象转换为json写出去
11、AbstractGenericHttpMessageConverter.class
最终所有容器底层的 HttpMessageConverter中的MappingJackson2HttpMessageConverter
把对象转为JSON形式写出去
1、引入xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2、yaml配置文件开启浏览器参数方式内容协商功能,开启之后在浏览器输入http://localhost:8081/responseData?format=xml
即可开启基于xml的请求参数
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
注意
:使用上述浏览器url方式请求参数是基于参数的策略(ParameterContentNegotiationStrategy),使用下面postman请求参数是基于请求头的策略(HeaderContentNegotiationStrategy)
3、postman分别测试返回json和xml
4、内容协商原理
1)获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】。
List acceptableTypes = this.getAcceptableMediaTypes( request);
注:getAcceptableMediaTypes方法底层使用了contentNegotiationManager
内容协商管理器,默认使用基于请求头的策略。也就是ContentNegotiationStrategy的实现类HeaderContentNegotiationStrategy
来确定客户端可以接收的内容类型
2)遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象,把converter支持的媒体类型统计出来。客户端需要【application/xml】。服务端能力【10种】
List producibleTypes = this.getProducibleMediaTypes(request,valueType,(Type)tangetType);
3)进行内容协商的最佳匹配媒体类型,用支持将对象转为最佳匹配媒体类型的converter,调用它进行转化 。
Iterator var23 = this.messageConverters.iterator();
while(var23.hasNext()) {
converter = (HttpMessageConverter)var23.next();
genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
if (genericConverter != null) {
if (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {
break label183;
}
} else if (converter.canWrite(valueType, selectedMediaType)) {
break label183;
}
}
1、自定义消息订制器类MyMessageConvert实现HttpMessageConverter
接口
public class MyMessageConvert implements HttpMessageConverter<Book> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Book.class);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-myForm");
}
@Override
public Book read(Class<? extends Book> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Book book, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String data = book.getBookId() + ";" + book.getBookName() + ";" + book.getPrice() + ";" + book.getStock();
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
2、WebMvcConfigurer接口有两个相关方法,configureMessageConverters是覆盖默认消息订制器
extendMessageConverters
是追加消息订制器。我们自定义消息订制器需要实现extendMessageConverters方法
@Component
public class MyConfig {
@Bean
public WebMvcConfigurer myWebMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyMessageConvert());
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("xml", MediaType.APPLICATION_XML);
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("myForm", MediaType.parseMediaType("application/x-myForm"));
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
// HeaderContentNegotiationStrategy headStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy));
}
};
}
}
注意
:因为是自定义消息订制器,通过浏览器url请求参数,所以contentNegotiationManager内容协商管理器是ParameterContentNegotiationStrategy
基于参数策略,但它只有xml和json两种格式,所以要实现configureContentNegotiation
方法增加自定义格式。但是该方法慎用
!因为它会覆盖默认很多功能,导致一些默认的功能失效。
例:
实现configureContentNegotiation之前
,通过postman访问正常,浏览器访问异常
实现configureContentNegotiation之后
,基于请求头的策略消失了,只剩下基于参数的。此时浏览器访问虽然正常,但是postman解析处理的acceptableTypes的值为"*/*",对于任何类型的格式都匹配,风险较高!因此configureContentNegotiation方法里需要通过new HeaderContentNegotiationStrategy()来增加请求头策略
3、配置yaml文件
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
4、为客户端提供访问接口:localhost:8081/responseData?format=myForm
@Controller
public class UserController {
@Autowired
private UserService userService;
@ResponseBody
@RequestMapping(value = "/requestBook", method = RequestMethod.POST)
public Book requestBook(Book book){
return book;
}
@ResponseBody
@RequestMapping(value = "/responseData", method = RequestMethod.GET)
public Book responseData(){
Book book = new Book();
book.setBookId(1);
book.setBookName("三体");
book.setPrice(100);
book.setStock(10);
return book;
}
}