做网站需要什么配置的电脑营销号经典废话
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
- 写在前面的话
 - MessageConverters
 - 技术说明
 - 基础示例
 - 执行过程
 - 注意事项
 
- 源码知识回顾
 - 总结陈词
 

写在前面的话
前几篇博文,大致了解了SpringMVC请求流程中的参数与返回值的源码分析,后续的几篇博文,会将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC带来的定制和扩展能力。
 本篇文章先介绍一下 MessageConverters 相关内容。
相关博文
 《学会 SpringMVC 系列 · 基础篇》
 《学会 SpringMVC 系列 · 剖析篇(上)》
 《学会 SpringMVC 系列 · 剖析入参处理》
 《学会 SpringMVC 系列 · 剖析出参处理》
 《学会 SpringMVC 系列 · 返回值处理器》
 《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
MessageConverters
技术说明
作用:MessageConverters 主要负责将 Controller 方法的返回值转换为 HTTP 响应的内容。
 工作原理:当 Controller 方法返回一个对象时,Spring MVC 使用消息转换器将该对象转换为 HTTP 响应体的内容。消息转换器负责将 Java 对象转换为特定的媒体类型,例如 JSON、XML、HTML 等。Spring 提供了各种内置的消息转换器来支持不同的数据格式。
 示例:如果你的 Controller 方法返回一个对象,Spring MVC 将根据请求的 Accept 头部信息和返回值类型选择适当的消息转换器,将对象转换为对应的媒体类型。
基础示例
描述:写了一个测试的效果,针对Student类型入参做了一个特定转换操作,部分代码见下方。
 补充:要实现自定义消息转换器(入参和出参都适用),就继承 AbstractHttpMessageConverter,实现相应方法,有点类似参数解析器。
 Step1、自定义消息转换器
public class MyMessageConverter extends AbstractHttpMessageConverter<Student> {/*** 新建一个我们自定义的媒体类型application/xxx-lw*/public MyMessageConverter() {super(new MediaType("application", "xxx-lw", StandardCharsets.UTF_8));}/*** 支持的类型*/@Overrideprotected boolean supports(Class<?> clazz) {return Student.class.isAssignableFrom(clazz);}/*** 入参处理器*/@Overrideprotected Student readInternal(Class<? extends Student> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {String str = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);String[] split = str.split(",");String name = split[0].split("#")[1];String age = split[1].split("#")[1];return Student.builder().name(name).age(Integer.parseInt(age)).id(1).build();}/*** 重写writeInternal ,处理如何输出数据到response。*/@Overrideprotected void writeInternal(Student user, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {String outStr = "获取到名称为" + user.getName() + ",年龄" + user.getAge() + "岁的人";outputMessage.getBody().write(outStr.getBytes());}
}
 
Step2、配置消息转换器
//下方两个配置方式,保留一个,是JSON序列化解析的逻辑,和MyMessageConverter无关/*** 扩展原有方式实现*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {for (HttpMessageConverter<?> httpMessageConverter : converters) {if (MappingJackson2HttpMessageConverter.class.isAssignableFrom(httpMessageConverter.getClass())) {MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) httpMessageConverter;ObjectMapper objectMapper = mappingJackson2HttpMessageConverter.getObjectMapper();objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {@Overridepublic void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {jsonGenerator.writeString("");}});mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);}}converters.add(0, new MyMessageConverter());
}/*** 添加自定义消息转换器* BigInteger转String* NULL转空字符串*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();ObjectMapper objectMapper = SpringContextHolder.getBean(ObjectMapper.class);SimpleModule simpleModule = new SimpleModule();simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);objectMapper.registerModule(simpleModule);jackson2HttpMessageConverter.setObjectMapper(objectMapper);converters.add(0, jackson2HttpMessageConverter);
}
 
Step3、编写测试接口
/*** 测试自定义消息转换器*/
@RequestMapping("/stuTest")
public Student stuTest(@RequestBody Student stu) {return Student.builder().id(1).name(stu.getName()).age(stu.getAge()).email(null).build();
}
 
Step4、启动服务,PostMan测试效果,信息如下:
curl --location 'http://localhost:8083/stuTest' \
--header 'Content-Type: application/xxx-lw' \
--header 'Cookie: JSESSIONID=2BFDA24061FF974C50BECD540FB916D1' \
--data 'name#张三,age#20'
 
返回结果:获取到名称为张三,年龄20岁的人
执行过程
1、由于添加了@RequestBody,直接进入 RequestResponseBodyMethodProcessor#resolveArgument;
 2、接着代码走到 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters,选取合适的入参转换;
 3、由于 MyMessageConverter 是首个转换器,supports 方法也满足,被触发其 read 方法,实际是 MyMessageConverter#readInternal;
 4、接着执行核心业务方法;
 5、接着代码走到 AbstractMessageConverterMethodArgumentResolver#writeWithMessageConverters,选取合适的出参转换;
 6、这里 MyMessageConverter 继续被匹配,执行 write 方法,实际是 MyMessageConverter#writeInternal;
 7、最后就是 outputMessage.getBody().flush(),流程结束。
Tips:这里看到read前后,有beforeBodyRead和afterBodyRead,也都是可以扩展的,write 只有 beforeBodyWrite 方法,因为写完就结束了。
【截图补充】
 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters,可以看到注册的自定义消息转换器已经在第一个,判断符合要求后会执行。
 可以看出找到符合的消息转换器,直接break了,代表消息转换器只会执行一个。
 
注意事项
如果接口入参去掉RequestBody注解,再测试一下。
 那现象是进入自定义的入参解析器,不会进入入参转换器。但由于结果还是ResponseBody,因此还是会进入出参转换器。
 这个也是很好理解的!
源码知识回顾
本篇为 SpringMVC 源码分析系列文章,正片开始前,先总结回顾一下全流程。
【一次请求的主链路节点】
 DispatcherServlet#doDispatch(入口方法)
 DispatcherServlet#getHandler(根据path找到对应的HandlerExecutionChain)
 DispatcherServlet#getHandlerAdapter(根据handle找到对应的HandlerAdapter)
 HandlerExecutionChain#applyPreHandle(触发拦截器的前置逻辑)
 AbstractHandlerMethodAdapter#handle(核心逻辑)
 HandlerExecutionChain#applyPostHandle(触发拦截器的后置逻辑)
【核心handle方法的主链路节点】
 RequestMappingHandlerAdapter#handleInternal(入口方法)
 RequestMappingHandlerAdapter#invokeHandlerMethod(入口方法2)
 ServletInvocableHandlerMethod#invokeAndHandle(入口方法3)
 InvocableHandlerMethod#invokeForRequest(参数和实际执行的所在,3.1)
 InvocableHandlerMethod#getMethodArgumentValues(参数处理,3.1.1)
 InvocableHandlerMethod#doInvoke(实际执行,3.1.2)
 HandlerMethodReturnValueHandlerComposite#handleReturnValue(返回处理,3.2)
 
【针对 @RequestBody 和 @ResponseBody 场景】
 
总结陈词
本篇博文继请求链路源码分析后,继续介绍了MessageConverters的用法,它既可以用在入参处理,也可以用于返回值处理,挺方便的,欲知后事如何,请听下回分解。
 💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

