SpringMVC的HttpMessageConverter(消息转换器)

您所在的位置:网站首页 springboot扩展springmvc SpringMVC的HttpMessageConverter(消息转换器)

SpringMVC的HttpMessageConverter(消息转换器)

2023-08-04 10:14| 来源: 网络整理| 查看: 265

首先说说我在阅读Spring官方文档遇到的三个不明确的地方,

Type Conversion( 类型转换)、Content Type(内容类型) 、Message Converters(消息转换器)

TypeConversion(类型转换器)

首先看看spring的官网怎么解释这个的:

By default, formatters for various number and date types are installed, along with support for customization via @NumberFormat and @DateTimeFormat on fields.

大意大概是:

默认情况下,会安装各种数字和日期类型的格式化器,并支持通过@NumberFormat和@DateTimeFormat对字段进行定制。

下面来看看一份自己添加自定义的类型转换器的xml文件配置

通过这段配置我们可以看出所有自定义的类型转换器都会加入到FormattingConversionServiceFactoryBean类中,下面我们来看看这个类的源码是怎么写的:

public class FormattingConversionServiceFactoryBean implements FactoryBean, EmbeddedValueResolverAware, InitializingBean { //我们加的所有自定义的类型转换器都用这个容器装起来的 private Set converters; //格式化器(我还没用过。) private Set formatters; private Set formatterRegistrars; private boolean registerDefaultFormatters = true; /** * 配置应添加的一组的自定义的转换器 */ public void setConverters(Set converters) { this.converters = converters; } /** * 配置应添加的一组自定义格式化程序对象。 */ public void setFormatters(Set formatters) { this.formatters = formatters; } /** * 设置格式化注册器 */ public void setFormatterRegistrars(Set formatterRegistrars) { this.formatterRegistrars = formatterRegistrars; } /** * 是否注册默认格式化器 */ public void setRegisterDefaultFormatters(boolean registerDefaultFormatters) { this.registerDefaultFormatters = registerDefaultFormatters; } //注册格式化器 private void registerFormatters() { ... installFormatters(this.conversionService); } /** * 钩子方法(用于被子类重写) */ @Deprecated protected void installFormatters(FormatterRegistry registry) { } } ContentType(内容类型)

老规矩,先看官网解释:

You can configure how Spring MVC determines the requested media types from the request (for example, Accept header, URL path extension, query parameter, and others).By default, the URL path extension is checked first — with json, xml, rss, and atom registered as known extensions (depending on classpath dependencies). The Accept header is checked second.Consider changing those defaults to Accept header only, and, if you must use URL-based content type resolution, consider using the query parameter strategy over path extensions. See Suffix Match and Suffix Match and RFD for more details.

大意翻译大概是:

您可以配置Spring MVC如何从请求中确定所请求的媒体类型(例如,Accept头、URL路径扩展、查询参数等)。

默认情况下,首先检查URL路径扩展—将json、xml、rss和atom注册为已知的扩展(取决于类路径依赖关系)。然后检查Accept报头。

考虑将这些默认值更改为只接受头,如果必须使用基于url的内容类型解析,则考虑在路径扩展上使用查询参数策略。有关更多细节,请参阅后缀匹配和后缀匹配以及RFD。

我划着这东西应该跟url路径映射有关,就是根据你传递过来的内容类型的后缀名去匹配对应的url。就像RESTFUL风格的GET、POST、PUT和DELETE请求方式一样

Message Converters(消息转换器)

首先这个东西就不比之前那两个了,想了解MessageConverters那就必须先要了解这里说的消息是指的什么

消息

就类似于我们平时发起的TCP/IP请求的,每次在向服务器发起一个请求时,这个时候就会产生请求报文,而服务器在响应我们发起的请求之后又会产生响应报文。而在这其中对象(你总是要把数据封装成对象返回的)就是最重要的承载消息的容器,而将这些报文和对象的相互转换就是消息转化器的作用。

在这里插入图片描述

一个是请求头信息(Request Header),一个是响应头信息(Response Headers)

消息转换器

在Spring中我们平时在控制器经常打的两个注解@RequestBody和@ResponseBody

@ResponseBody @RequestMapping("/") public Map sayHello(String hello) { Map map = new HashMap(); map.put(hello, "hello"); return map; } @ResponseBody @RequestMapping("/hello") public Object sayHello(@RequestBody ClientTestSpring clientTestSpring) { return clientTestSpring; }

当然要想这两个注解有效那就还得在.xml文件中配置消息转换器

消息转换的流程 HttpServletRequest和HttpServletResponse

首先我们每一次的请求内容都会封装在Request对象中(写过servlet的都应该知道,请求类:HttpServletRequest),而javax.servlet.http.HttpServletRequest这个接口继承了javax.servlet.ServletRequest接口,在父接口中可以看到有一个方法可以获取到请求头信息的流的一个方法:

public ServletInputStream getInputStream() throws IOException;

得到一个ServletInputStream。这个ServletInputStream中,可以读取到一个原始请求报文的所有内容。同样的,在javax.servlet.ServletResponse接口中,可以用以下方法:

public ServletOutputStream getOutputStream() throws IOException;

得到一个ServletOutputStream。这个ServletOutputStream中,可以读取到一个原始响应报文的所有内容。下面我们来看HttpMessageConverter

public interface HttpMessageConverter { //判断我这个消息转换器是否有读取的能力(和read()方法搭配使用) boolean canRead(Class clazz, MediaType mediaType); //判断我这个消息转换器是否有写的能力(和write()方法搭配使用) boolean canWrite(Class clazz, MediaType mediaType); List getSupportedMediaTypes(); T read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }

知道请求头信息和响应头信息在那了,那SpringMVC现在的工作就是去获取数据了,而读取数据和写数据我们可以从源码中看出在read()方法和writer()方法中有两个对象分别是:__HttpInputMessage__ 和__HttpOutputMessage__,源码如下:

public interface HttpInputMessage extends HttpMessage { InputStream getBody() throws IOException; } public interface HttpOutputMessage extends HttpMessage { OutputStream getBody() throws IOException; }

大致说一下这两个接口的工作流程:

HttpInputMessage 这个类是SpringMVC内部对一次Http请求报文的抽象,在HttpMessageConverter的read()方法中,有一个HttpInputMessage的形参,它正是SpringMVC的消息转换器所作用的受体“请求消息”的内部抽象,消息转换器从“请求消息”中按照规则提取消息,转换为方法形参中声明的对象。通俗点就是,你打的@RequesBody注解要映射的参数值的数据就封装在这个接口某一个实现类中

HttpOutputMessage 这个类是SpringMVC内部对一次Http响应报文的抽象,在HttpMessageConverter的write()方法中,有一个HttpOutputMessage的形参,它正是SpringMVC的消息转换器所作用的受体“响应消息”的内部抽象,消息转换器将“响应消息”按照一定的规则写到响应报文中。通俗点就是,你打的@ResponseBody注解要映射的返回值的数据就封装在这个接口某一个实现类中

假如现在我们开始向服务器发起了一个http请求,然后被我们的控制器通过@RequestMapper注解跳转到相应的方法中,如下方法:

@ResponseBody @RequestMapping("/") public String sayHello(String hello) { return hello+"你好,世界!!"; } @ResponseBody @RequestMapping("/hello") public Object sayHello(@RequestBody ClientTestSpring clientTestSpring) { return clientTestSpring; }

在SpringMVC进入sayHello方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到sayHello()方法的string变量中。

当SpringMVC执行sayHello()方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。

大致过程如下图:

在这里插入图片描述

RequestResponseBodyMethodProcessor解析

这个类集成了参数解析和响应解析

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); } @Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null || returnType.getMethodAnnotation(ResponseBody.class) != null); } /* * 解析参数 */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name); if (argument != null) { validate(binder, parameter); } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return argument; } /** * 解析响应 */ @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException { mavContainer.setRequestHandled(true); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, webRequest); } }

继承树如下: 在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3