消息转换器的工作机制
内部工作流程
-
读取(Read)操作
- 当接收到一个包含实体内容的HTTP请求时,Spring MVC会根据请求头中的
Content-Type
属性来确定应该使用哪个HttpMessageConverter
来解析请求体。 DispatcherServlet
会遍历已注册的HttpMessageConverter
列表,寻找支持该Content-Type
的转换器。- 一旦找到匹配的转换器,它将调用
readInternal()
方法,将输入流中的数据转换成Java对象,并将其作为控制器方法的参数传递。
- 当接收到一个包含实体内容的HTTP请求时,Spring MVC会根据请求头中的
-
写入(Write)操作
- 在响应阶段,如果控制器方法返回了一个对象(例如通过
@ResponseBody
或ResponseEntity
),Spring MVC会根据客户端请求头中的Accept
属性选择最合适的HttpMessageConverter
。 - 它再次遍历
HttpMessageConverter
列表,这次是基于Accept
头来决定哪个转换器最适合用于序列化返回的对象。 - 找到匹配的转换器后,它会调用
writeInternal()
方法,将Java对象转换为输出流格式的数据,并设置响应的内容类型。
- 在响应阶段,如果控制器方法返回了一个对象(例如通过
-
优先级规则
- 如果有多个
HttpMessageConverter
都能处理相同的媒体类型,Spring MVC会根据它们的优先级来选择最合适的一个。优先级通常由实现类自身定义,也可以通过配置调整。
- 如果有多个
-
支持的内容类型
- 每个
HttpMessageConverter
都声明了它能够处理的一组媒体类型(MIME types)。例如,MappingJackson2HttpMessageConverter
通常支持application/json
,而Jaxb2RootElementHttpMessageConverter
则可能支持application/xml
等。 - 这意味着只有当请求或响应的
Content-Type
或Accept
头与某个转换器支持的媒体类型相匹配时,该转换器才会被选中进行实际的数据转换工作。
- 每个
数据绑定与校验
-
数据绑定:Spring MVC框架可以自动地将HTTP请求中的查询参数、路径变量以及表单字段绑定到控制器方法的参数上。对于复杂的对象结构,如JSON或XML格式的数据,则需要
HttpMessageConverter
来进行反序列化。 -
校验:如果控制器方法参数上有
@Valid
或@Validated
注解,Spring会调用校验框架(如Hibernate Validator)对传入的对象进行验证。如果校验失败,可以抛出异常或者返回错误信息给客户端。
配置HttpMessageConverter
开发者可以通过多种方式配置HttpMessageConverter
:
-
全局配置:在Spring Boot应用中,可以在
application.properties
或application.yml
文件中指定默认的HttpMessageConverter
设置。例如,你可以调整Jackson库的行为以改变JSON序列化/反序列化的规则。 -
编程式配置:通过创建一个实现了
WebMvcConfigurer
接口的配置类,并重写extendMessageConverters
方法,你可以添加、移除或自定义HttpMessageConverter
实例。
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {// 添加或修改现有的HttpMessageConverterconverters.add(new CustomHttpMessageConverter());}
}
- 自定义转换器:如果你的应用需要处理非标准的数据格式,你可以编写自己的
HttpMessageConverter
实现,并将其添加到Spring MVC的转换器列表中。
请求处理流程中的具体运作
以下是更为详细的请求处理流程:
-
接收HTTP请求:Web服务器接收到HTTP请求并转发给
DispatcherServlet
。 -
HandlerMapping选择处理器:
DispatcherServlet
根据URL模式或其他条件查找适当的控制器方法。 -
拦截器链预处理:如果有配置拦截器,它们会在请求到达目标处理器之前进行预处理。
-
准备调用控制器方法
-
解析请求参数:
DispatcherServlet
检查是否有任何@RequestBody
标注的方法参数。如果是,它会遍历已注册的HttpMessageConverter
,寻找能处理当前Content-Type
的转换器,并使用它来解析请求体为Java对象。 -
数据验证:如果存在
@Valid
或@Validated
注解,Spring会调用校验框架对传入的对象进行校验。
-
-
调用控制器方法:所有准备工作完成后,
DispatcherServlet
调用相应的控制器方法。 -
处理方法返回值
-
视图解析:如果控制器方法返回的是视图名称或
ModelAndView
,ViewResolver
会负责渲染视图。 -
响应体转换:对于标记了
@ResponseBody
的方法,或者是返回了ResponseEntity
、HttpEntity
类型的对象,Spring会再次遍历HttpMessageConverter
列表,根据Accept
头选择适合的转换器,将返回的对象序列化为HTTP响应体。
-
-
拦截器链后处理:如果有配置拦截器,它们可以在控制器方法执行后进行后处理。
-
发送响应:
DispatcherServlet
构建完整的HTTP响应,并发送回客户端。 -
结束请求:整个请求-响应周期完成,资源释放,等待下一个请求到来。
HttpMessageConverter示例
接下来,我们将详细展示如何创建和配置一个自定义的消息转换器,用于处理特定的文本格式(假设是CSV格式)。
自定义HttpMessageConverter实现
首先,我们需要创建一个新的类继承自AbstractHttpMessageConverter<T>
,其中T
是我们想要转换的目标类型。在这个例子中,我们将处理List<String>
类型的CSV数据。
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;public class CsvHttpMessageConverter extends AbstractHttpMessageConverter<List<String>> {public CsvHttpMessageConverter() {super(MediaType.parseMediaType("text/csv"));}@Overrideprotected boolean supports(Class<?> clazz) {return List.class.isAssignableFrom(clazz);}@Overrideprotected List<String> readInternal(Class<? extends List<String>> clazz, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException {try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputMessage.getBody(), "UTF-8"))) {String line;List<String> result = new ArrayList<>();while ((line = reader.readLine()) != null) {result.addAll(List.of(line.split(",")));}return result;}}@Overrideprotected void writeInternal(List<String> t, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {try (OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), "UTF-8")) {for (int i = 0; i < t.size(); i++) {writer.write(t.get(i));if (i < t.size() - 1) {writer.write(",");}}}}
}
注册自定义HttpMessageConverter
然后,我们需要在Spring配置中注册这个自定义的消息转换器。这可以通过实现WebMvcConfigurer
接口并在其extendMessageConverters
方法中添加我们的转换器来完成。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new CsvHttpMessageConverter());}
}
使用自定义HttpMessageConverter
最后,我们可以在控制器中使用这个自定义的消息转换器来处理CSV格式的数据。例如:
@RestController
@RequestMapping("/csv")
public class CsvController {@PostMapping(consumes = "text/csv", produces = "text/csv")public ResponseEntity<List<String>> handleCsv(@RequestBody List<String> csvData) {// 处理CSV数据...return ResponseEntity.ok(csvData);}
}
在这个例子中,我们创建了一个名为CsvHttpMessageConverter
的类,它可以处理text/csv
类型的数据。然后我们在配置类中将这个自定义的转换器添加到了Spring MVC的HttpMessageConverter
列表中。通过这种方式,你可以根据应用程序的具体需求定制化消息转换过程,确保你的API能够正确地解析和生成各种格式的数据。
总结
综上所述,HttpMessageConverter
在Spring MVC中扮演着至关重要的角色,它使得开发者可以专注于业务逻辑,而无需关心如何从HTTP请求中解析数据或如何构建HTTP响应。通过深入了解其工作机制、配置方法以及如何扩展和自定义转换器,我们可以更好地利用Spring MVC的强大功能来构建高效、灵活的Web应用程序。