SpringBoot响应处理源码分析以及自定义MessageConverter

文章详细阐述了SpringMVC如何将自定义POJO类通过@ResponseBody注解转化为JSON格式响应数据的内部机制,包括从RequestMappingHandlerAdapter到HttpMessageConverter的过程,以及内容协商的策略,如基于请求头和请求参数的策略。同时,文章还介绍了如何自定义MessageConverter并配置内容协商以支持自定义格式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自定义pojo类通过@ResponseBody响应数据为json格式源码解析

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<MediaType> 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;
	}
}

在这里插入图片描述

自定义MessageConverter

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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值