【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---HttpMessageConverter 消息转换器详解(上)

简介: 【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---HttpMessageConverter 消息转换器详解(上)

前言


本文介绍Spring MVC中的一个极其重要的组件:HttpMessageConverter消息转换器。

有一副非常著名的图,来形容Spring MVC对一个请求的处理:


image.png


从图中可见HttpMessageConverter对Spring MVC的重要性。它对请求、响应都起到了非常关键的作用~


为何需要消息转换器


HttpMessageConverter是用来处理request和response里的数据的。.


请求和响应都有对应的body,而这个body就是需要关注的主要数据。

请求体的表述一般就是一段字符串,当然也可以是二进制数据(比如上传~)。

响应体则是浏览器渲染页面的依据,对于一个普通html页面得响应,响应体就是这个html页面的源代码。


请求体和响应体都是需要配合Content-Type头部使用的,这个头部主要用于说明body中得字符串是什么格式的,比如:text,json,xml等。对于请求报文,只有通过此头部,服务器才能知道怎么解析请求体中的字符串,对于响应报文,浏览器通过此头部才知道应该怎么渲染响应结果,是直接打印字符串还是根据代码渲染为一个网页

对于HttpServletRequest和HttpServletResponse,可以分别调用getInputStream和getOutputStream来直接获取body。但是获取到的仅仅只是一段字符串

**而对于java来说,处理一个对象肯定比处理一个字符串要方便得多,也好理解得多。**所以根据Content-Type头部,将body字符串转换为java对象是常有的事。反过来,根据Accept头部,将java对象转换客户端期望格式的字符串也是必不可少的工作。这就是我们本文所讲述的消息转换器的工作~


消息转换器它能屏蔽你对底层转换的实现,分离你的关注点,让你专心操作java对象,其余的事情你就交给我Spring MVC吧~大大提高你的编码效率(可议说比源生Servlet开发高级太多了)


Spring内置了很多HttpMessageConverter,比如MappingJackson2HttpMessageConverter,StringHttpMessageConverter,甚至还有FastJsonHttpMessageConverter(需导包和自己配置)


HttpMessageConverter


在具体讲解之前,先对所有的转换器来个概述:

image.png


image.png

Jaxb也是和Sax、Dom、JDOM类似的解析XML的类库,jackson-module-jaxb-annotations对它提供了支持,但是由于关注太少了,所以Jaxb相关的转换器此处省略~~~

MarshallingHttpMessageConverter也是Spring采用Marshaller/Unmarshaller的方式进行xml的解析,也不关注了

FastJsonHttpMessageConverter4和FastJsonpHttpMessageConverter4都继承自FastJsonHttpMessageConverter,现在都已经标记为过期。直接使用FastJsonHttpMessageConverter它即可


需要知道的是:上面说的支持都说的是默认支持,当然你是可以自定义让他们更强大的。比如:我们可以自己配置StringHttpMessageConverter,改变(增强)他的默认行为:


<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/plain;charset=UTF-8</value>
                    <value>text/html;charset=UTF-8</value>
                </list>
             </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>


talk is cheap,show me the code,我们还是从代码的角度,直接看问题吧。


既然它是HttpMessageConverter,所以铁定和HttpMessage有关,因为此接口涉及的内容相对来说比较偏底层,因此本文只在接口层面做简要的一个说明。


HttpMessage


它是Spring 3.0后增加一个非常抽象的接口。表示:表示HTTP请求和响应消息的基本接口

public interface HttpMessage {
  // Return the headers of this message
  HttpHeaders getHeaders();
}


看看它的继承树:


image.png


HttpInputMessage和HttpOutputMessage

这就是目前都在使用的接口,表示输入、输出信息~


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

HttpRequest

代表着一个Http请求信息,提供了多的几个API,是对HttpMessage的一个补充。Spring3.1新增的

public interface HttpRequest extends HttpMessage {
  @Nullable
  default HttpMethod getMethod() {
    // 可议根据String类型的值  返回一个枚举
    return HttpMethod.resolve(getMethodValue());
  }
  String getMethodValue();
  // 可以从请求消息里  拿到URL
  URI getURI();
}


ReactiveHttpInputMessage和ReactiveHttpOutputMessage


显然,是Spring5.0新增的接口,也是Spring5.0最重磅的升级之一。自此Spring容器就不用强依赖于Servlet容器了。它还可以选择依赖于reactor这个框架。

比如这个类:reactor.core.publisher.Mono就是Reactive的核心类之一~


因为属于Spring5.0的最重要的新特性之一,所以此处也不再过多介绍了。后面会是重磅内容~


HttpMessageConverter接口是Spring3.0之后新增的一个接口,它负责将请求信息转换为一个对象(类型为T),并将对象(类型为T)绑定到请求方法的参数中或输出为响应信息

// @since 3.0  Spring3.0后推出的   是个泛型接口
// 策略接口,指定可以从HTTP请求和响应转换为HTTP请求和响应的转换器
public interface HttpMessageConverter<T> {
  // 指定转换器可以读取的对象类型,即转换器可将请求信息转换为clazz类型的对象
  // 同时支持指定的MIME类型(text/html、application/json等)
  boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
  // 指定转换器可以将clazz类型的对象写到响应流当中,响应流支持的媒体类型在mediaType中定义
  boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
  // 返回当前转换器支持的媒体类型~~
  List<MediaType> getSupportedMediaTypes();
  // 将请求信息转换为T类型的对象 流对象为:HttpInputMessage
  T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
  // 将T类型的对象写到响应流当中,同事指定响应的媒体类型为contentType 输出流为:HttpOutputMessage 
  void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}


看看它的继承树:


image.png

image.png


它的继承树,用品牌繁多来形容真的非常贴切。

按照层级划分,它的直接子类是如下四个:

FormHttpMessageConverter、AbstractHttpMessageConverter、BufferedImageHttpMessageConverter、GenericHttpMessageConverter(Spring3.2出来的,支持到了泛型)


FormHttpMessageConverter:form表单提交/文件下载


从名字知道,它和Form表单有关。浏览器原生表单默认的提交数据的方式(就是没有设置enctype属性),它默认是这个:Content-Type: application/x-www-form-urlencoded;charset=utf-8


从请求和响应读取/编写表单数据。默认情况下,它读取媒体类型 application/x-www-form-urlencoded 并将数据写入 MultiValueMap<String,String>。因为它独立的存在,所以可以看看源码内容:


// @since 3.0
public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
  // 默认UTF-8编码  MediaType为:application/x-www-form-urlencoded
  public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
  private static final MediaType DEFAULT_FORM_DATA_MEDIA_TYPE = new MediaType(MediaType.APPLICATION_FORM_URLENCODED, DEFAULT_CHARSET);
  // 缓存下它所支持的MediaType们
  private List<MediaType> supportedMediaTypes = new ArrayList<>();
  // 用于二进制内容的消息转换器们~~~ 毕竟此转换器还支持`multipart/form-data`这种  可以进行文件下载~~~~~
  private List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
  private Charset charset = DEFAULT_CHARSET;
  @Nullable
  private Charset multipartCharset;
  // 唯一的一个构造函数~
  public FormHttpMessageConverter() {
    // 默认支持处理两种MediaType:application/x-www-form-urlencoded和multipart/form-data
    this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
    this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316
    // === 它自己不仅是个转换器,还内置了这三个转换器 至于他们具体处理那种消息,请看下面 都有详细说明 ==
    // 注意:这些消息转换器都是去支持part的,支持文件下载
    this.partConverters.add(new ByteArrayHttpMessageConverter());
    this.partConverters.add(stringHttpMessageConverter);
    this.partConverters.add(new ResourceHttpMessageConverter());
    // 这是为partConverters设置默认的编码~~~
    applyDefaultCharset();
  }
  // 省略属性额get/set方法
  // 从这可以发现,只有Handler的入参类型是是MultiValueMap它才会去处理~~~~
  @Override
  public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
    if (!MultiValueMap.class.isAssignableFrom(clazz)) {
      return false;
    }
    // 若没指定MedieType  会认为是可读的~
    if (mediaType == null) {
      return true;
    }
    // 显然,只有我们Supported的MediaType才会是true(当然multipart/form-data例外,此处是不可读的)
    for (MediaType supportedMediaType : getSupportedMediaTypes()) {
      // We can't read multipart....
      if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) && supportedMediaType.includes(mediaType)) {
        return true;
      }
    }
    return false;
  }
  // 注意和canRead的区别,有点对着干的意思~~~
  @Override
  public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
    if (!MultiValueMap.class.isAssignableFrom(clazz)) {
      return false;
    }
    // 如果是ALL 说明支持所有的类型  那就恒返回true  当然null也是的
    if (mediaType == null || MediaType.ALL.equals(mediaType)) {
      return true;
    }
    for (MediaType supportedMediaType : getSupportedMediaTypes()) {
      // isCompatibleWith是否是兼容的
      if (supportedMediaType.isCompatibleWith(mediaType)) {
        return true;
      }
    }
    return false;
  }
  // 把输入信息读进来,成为一个 MultiValueMap<String, String>
  // 注意:此处发现class这个变量并没有使用~
  @Override
  public MultiValueMap<String, String> read(@Nullable Class<? extends MultiValueMap<String, ?>> clazz,
      HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    // 拿到请求的ContentType请求头~~~~
    MediaType contentType = inputMessage.getHeaders().getContentType();
    // 这里面 编码放在contentType里面  若没有指定  走默认的编码
    // 类似这种形式就是我们自己指定了编码:application/json;charset=UTF-8
    Charset charset = (contentType != null && contentType.getCharset() != null ? contentType.getCharset() : this.charset);
    // 把body的内容读成字符串~
    String body = StreamUtils.copyToString(inputMessage.getBody(), charset);
    // 用"&"分隔   因为此处body一般都是hello=world&fang=shi这样传进来的
    String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
    MultiValueMap<String, String> result = new LinkedMultiValueMap<>(pairs.length);
    // 这个就不说了,就是把键值对保存在map里面。注意:此处为何用多值Map呢?因为一个key可能是会有多个value的
    for (String pair : pairs) {
      int idx = pair.indexOf('=');
      if (idx == -1) {
        result.add(URLDecoder.decode(pair, charset.name()), null);
      }
      else {
        String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
        String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
        result.add(name, value);
      }
    }
    return result;
  }
}

相关文章
|
5月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
425 4
|
8月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
799 0
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
906 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
230 7
|
负载均衡 网络协议 应用服务中间件
web群集--rocky9.2源码部署nginx1.24的详细过程
Nginx 是一款由 Igor Sysoev 开发的开源高性能 HTTP 服务器和反向代理服务器,自 2004 年发布以来,以其高效、稳定和灵活的特点迅速成为许多网站和应用的首选。本文详细介绍了 Nginx 的核心概念、工作原理及常见使用场景,涵盖高并发处理、反向代理、负载均衡、低内存占用等特点,并提供了安装配置教程,适合开发者参考学习。
380 1
|
Java API 数据库
【神操作!】Spring Boot打造RESTful API:从零到英雄,只需这几步,让你的Web应用瞬间飞起来!
【8月更文挑战第12天】构建RESTful API是现代Web开发的关键技术之一。Spring Boot因其实现简便且功能强大而深受开发者喜爱。本文以在线图书管理系统为例,展示了如何利用Spring Boot快速构建RESTful API。从项目初始化、实体定义到业务逻辑处理和服务接口实现,一步步引导读者完成API的搭建。通过集成JPA进行数据库操作,以及使用控制器类暴露HTTP端点,最终实现了书籍信息的增删查改功能。此过程不仅高效直观,而且易于维护和扩展。
416 1
|
Java 开发者 前端开发
Struts 2、Spring MVC、Play Framework 上演巅峰之战,Web 开发的未来何去何从?
【8月更文挑战第31天】在Web应用开发中,Struts 2框架因强大功能和灵活配置备受青睐,但开发者常遇配置错误、类型转换失败、标签属性设置不当及异常处理等问题。本文通过实例解析常见难题与解决方案,如配置文件中遗漏`result`元素致页面跳转失败、日期格式不匹配需自定义转换器、`&lt;s:checkbox&gt;`标签缺少`label`属性致显示不全及Action中未捕获异常影响用户体验等,助您有效应对挑战。
274 0
|
Java 前端开发 Apache
Apache Wicket与Spring MVC等Java Web框架大PK,究竟谁才是你的最佳拍档?点击揭秘!
【8月更文挑战第31天】在Java Web开发领域,众多框架各具特色。Apache Wicket以组件化开发和易用性脱颖而出,提高了代码的可维护性和可读性。相比之下,Spring MVC拥有强大的生态系统,但学习曲线较陡;JSF与Java EE紧密集成,但在性能和灵活性上略逊一筹;Struts2虽成熟,但在RESTful API支持上不足。选择框架时还需考虑社区支持和文档完善程度。希望本文能帮助开发者找到最适合自己的框架。
279 0
|
Java Spring 开发者
Java Web开发新潮流:Vaadin与Spring Boot强强联手,打造高效便捷的应用体验!
【8月更文挑战第31天】《Vaadin与Spring Boot集成:最佳实践指南》介绍了如何结合Vaadin和Spring Boot的优势进行高效Java Web开发。文章首先概述了集成的基本步骤,包括引入依赖和配置自动功能,然后通过示例展示了如何创建和使用Vaadin组件。相较于传统框架,这种集成方式简化了配置、提升了开发效率并便于部署。尽管可能存在性能和学习曲线方面的挑战,但合理的框架组合能显著提升应用开发的质量和速度。
524 0