概述

FormHttpMessageConverterSpring Web提供的用于读写一般HTML表单数据的HttpMessageConverter实现类,也可以写multipart数据,但是不能读取multipart数据。

具体来讲,FormHttpMessageConverter 可以 :

  • 读写application/x-www-form-urlencoded媒体类型数据:MultiValueMap MultiValueMap<String, String>
  • multipart/form-data媒体类型数据:MultiValueMap MultiValueMap<String, Object>

    注意,不能读该类型数据,也就是不能用于处理文件上传。

FormHttpMessageConvertermultipart数据时,它还可以使用其他的HttpMessageConverter用于写每一个不同类型的MIME部分(part)。缺省情况下,用于写不同类型MIME部分(part)的HttpMessageConverter是一个StringHttpMessageConverter(字符集缺省使用UTF-8),一个ResourceHttpMessageConverter,还有一个ByteArrayHttpMessageConverter。当然使用者也可以通过#setPartConverters指定自己的HttpMessageConverter集合。

源代码

源代码版本 : spring-web-5.1.5.RELEASE

package org.springframework.http.converter;// 省略 imports public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {/*** The default charset used by the converter. 缺省使用字符集 utf-8*/public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;// 缺省支持的HTTP FORM 的MIME 格式 : application/x-www-form-urlencoded + utf-8private static final MediaType DEFAULT_FORM_DATA_MEDIA_TYPE =new MediaType(MediaType.APPLICATION_FORM_URLENCODED, DEFAULT_CHARSET);private List<MediaType> supportedMediaTypes = new ArrayList<>();// 记录用于操作 multipart 时,写各个 part 使用的 HttpMessageConverter 的集合private List<HttpMessageConverter<?>> partConverters = new ArrayList<>();// 处理二进制字节和字符串之间转换时要使用的字符集,缺省使用 UTF-8 private Charset charset = DEFAULT_CHARSET;@Nullableprivate Charset multipartCharset;public FormHttpMessageConverter() {// 添加缺省支持的 MIME 类型 :// 1. application/x-www-form-urlencoded// 2. multipart/form-datathis.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);// 缺省使用字符集 ISO-8859-1 的 StringHttpMessageConverterStringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316// 设置缺省使用的 multipart data  HttpMessageConverterthis.partConverters.add(new ByteArrayHttpMessageConverter());this.partConverters.add(stringHttpMessageConverter);this.partConverters.add(new ResourceHttpMessageConverter());// 检查 this.partConverters 中的每个 AbstractHttpMessageConverter , // 如果该 AbstractHttpMessageConverter/ 没有被设置缺省字符集,// 则将其缺省字符集设置为 this.charset,此时为 utf-8 applyDefaultCharset();}/*** Set the list of  MediaType objects supported by this converter.*/public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {this.supportedMediaTypes = supportedMediaTypes;}@Overridepublic List<MediaType> getSupportedMediaTypes() {return Collections.unmodifiableList(this.supportedMediaTypes);}/*** Set the message body converters to use. These converters are used to* convert objects to MIME parts.*/public void setPartConverters(List<HttpMessageConverter<?>> partConverters) {Assert.notEmpty(partConverters, "'partConverters' must not be empty");this.partConverters = partConverters;}/*** Add a message body converter. Such a converter is used to convert objects* to MIME parts.*/public void addPartConverter(HttpMessageConverter<?> partConverter) {Assert.notNull(partConverter, "'partConverter' must not be null");this.partConverters.add(partConverter);}/*** 设置请求/响应Content-Type头部没有明确设置字符集时读写HTML FORM数据* 使用的字符集,不设置的话缺省使用 UTF-8** Set the default character set to use for reading and writing form data when* the request or response Content-Type header does not explicitly specify it.* As of 4.3, this is also used as the default charset for the conversion* of text bodies in a multipart request.* As of 5.0 this is also used for part headers including* "Content-Disposition" (and its filename parameter) unless (the mutually* exclusive) #setMultipartCharset is also set, in which case part* headers are encoded as ASCII and filename is encoded with the* "encoded-word" syntax from RFC 2047.* By default this is set to "UTF-8".*/public void setCharset(@Nullable Charset charset) {if (charset != this.charset) {this.charset = (charset != null ? charset : DEFAULT_CHARSET);applyDefaultCharset();}}/*** Apply the configured charset as a default to registered part converters.* 检查 this.partConverters 中的每个 AbstractHttpMessageConverter , 如果该 AbstractHttpMessageConverter* 没有被设置缺省字符集,则将其缺省字符集设置为 this.charset*/private void applyDefaultCharset() {for (HttpMessageConverter<?> candidate : this.partConverters) {if (candidate instanceof AbstractHttpMessageConverter) {AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;// Only override default charset if the converter operates with a charset to begin with...if (converter.getDefaultCharset() != null) {converter.setDefaultCharset(this.charset);}}}}/*** Set the character set to use when writing multipart data to encode file* names. Encoding is based on the "encoded-word" syntax defined in RFC 2047* and relies on MimeUtility from "javax.mail".* As of 5.0 by default part headers, including Content-Disposition (and* its filename parameter) will be encoded based on the setting of* #setCharset(Charset) or UTF-8 by default.* @since 4.1.1* @see <a href="http://en.wikipedia.org/wiki/MIME#Encoded-Word">Encoded-Word</a>*/public void setMultipartCharset(Charset charset) {this.multipartCharset = charset;}// 是否可以读取某种指定类型的MediaType mediaType :// 1. 通过检查 supportedMediaTypes 是否包含 mediaType 判断// 2. null mediaType 被支持// 3. 不能读取 multipart/form-data 数据, 对此做了特殊处理    @Overridepublic boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {if (!MultiValueMap.class.isAssignableFrom(clazz)) {return false;}if (mediaType == null) {return true;}for (MediaType supportedMediaType : getSupportedMediaTypes()) {// We can't read multipart....if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) && supportedMediaType.includes(mediaType)) {return true;}}return false;}// 是否可以写入某种指定类型的MediaType mediaType :// 1. 通过检查 supportedMediaTypes 是否跟指定类型 mediaType 兼容判断// 2. null mediaType 被支持// 3. */* mediaType 被支持@Overridepublic boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {if (!MultiValueMap.class.isAssignableFrom(clazz)) {return false;}if (mediaType == null || MediaType.ALL.equals(mediaType)) {return true;}for (MediaType supportedMediaType : getSupportedMediaTypes()) {if (supportedMediaType.isCompatibleWith(mediaType)) {return true;}}return false;}@Overridepublic MultiValueMap<String, String> read(@Nullable Class<? extends MultiValueMap<String, ?>> clazz,HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {// 获取  inputMessage 消息的内容类型到 contentTypeMediaType contentType = inputMessage.getHeaders().getContentType();// 获取  inputMessage 消息内容类型 contentType 指定的字符集,// 如果 contentType 未指定字符集,使用当前对象的属性 this.charset , 缺省是 UTF-8Charset charset = (contentType != null && contentType.getCharset() != null ?contentType.getCharset() : this.charset);// 使用字符集 charset 将消息体字节读取成字符串,记录到 body         String body = StreamUtils.copyToString(inputMessage.getBody(), charset);// 对消息体进行分析,因为该类仅支持读取HTTP FORM数据,这里按照 HTTP FORM数据的在// HTTP request body 中的格式进行分析String[] pairs = StringUtils.tokenizeToStringArray(body, "&");MultiValueMap<String, String> result = new LinkedMultiValueMap<>(pairs.length);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;}// 写 FORM 数据到 outputMessage 消息体@Override@SuppressWarnings("unchecked")public void write(MultiValueMap<String, ?> map, @Nullable MediaType contentType, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {if (!isMultipart(map, contentType)) {writeForm((MultiValueMap<String, Object>) map, contentType, outputMessage);}else {writeMultipart((MultiValueMap<String, Object>) map, outputMessage);}}private boolean isMultipart(MultiValueMap<String, ?> map, @Nullable MediaType contentType) {if (contentType != null) {return MediaType.MULTIPART_FORM_DATA.includes(contentType);}for (String name : map.keySet()) {for (Object value : map.get(name)) {if (value != null && !(value instanceof String)) {return true;}}}return false;}// 写非 multipart FORM 数据到 outputMessage 消息体private void writeForm(MultiValueMap<String, Object> formData, @Nullable MediaType contentType,HttpOutputMessage outputMessage) throws IOException {contentType = getMediaType(contentType);outputMessage.getHeaders().setContentType(contentType);Charset charset = contentType.getCharset();Assert.notNull(charset, "No charset"); // should never occurfinal byte[] bytes = serializeForm(formData, charset).getBytes(charset);outputMessage.getHeaders().setContentLength(bytes.length);if (outputMessage instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(bytes, outputStream));}else {StreamUtils.copy(bytes, outputMessage.getBody());}}private MediaType getMediaType(@Nullable MediaType mediaType) {if (mediaType == null) {return DEFAULT_FORM_DATA_MEDIA_TYPE;}else if (mediaType.getCharset() == null) {return new MediaType(mediaType, this.charset);}else {return mediaType;}}protected String serializeForm(MultiValueMap<String, Object> formData, Charset charset) {StringBuilder builder = new StringBuilder();formData.forEach((name, values) ->values.forEach(value -> {try {if (builder.length() != 0) {builder.append('&');}builder.append(URLEncoder.encode(name, charset.name()));if (value != null) {builder.append('=');builder.append(URLEncoder.encode(String.valueOf(value), charset.name()));}}catch (UnsupportedEncodingException ex) {throw new IllegalStateException(ex);}}));return builder.toString();}// 写 multipart FORM 数据到 outputMessage 消息体private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)throws IOException {final byte[] boundary = generateMultipartBoundary();Map<String, String> parameters = new LinkedHashMap<>(2);if (!isFilenameCharsetSet()) {parameters.put("charset", this.charset.name());}parameters.put("boundary", new String(boundary, StandardCharsets.US_ASCII));MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);HttpHeaders headers = outputMessage.getHeaders();headers.setContentType(contentType);if (outputMessage instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;streamingOutputMessage.setBody(outputStream -> {writeParts(outputStream, parts, boundary);writeEnd(outputStream, boundary);});}else {writeParts(outputMessage.getBody(), parts, boundary);writeEnd(outputMessage.getBody(), boundary);}}/*** When #setMultipartCharset(Charset) is configured (i.e. RFC 2047,* "encoded-word" syntax) we need to use ASCII for part headers or otherwise* we encode directly using the configured  #setCharset(Charset).*/private boolean isFilenameCharsetSet() {return (this.multipartCharset != null);}// 写 multipart FORM 数据中的各个 part 到 outputMessage 消息体的输出流 osprivate void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException {for (Map.Entry<String, List<Object>> entry : parts.entrySet()) {String name = entry.getKey();for (Object part : entry.getValue()) {if (part != null) {writeBoundary(os, boundary);writePart(name, getHttpEntity(part), os);writeNewLine(os);}}}}// 写 multipart FORM 数据中的某个 part 到 outputMessage 消息体的输出流 os @SuppressWarnings("unchecked")private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {Object partBody = partEntity.getBody();if (partBody == null) {throw new IllegalStateException("Empty body for part '" + name + "': " + partEntity);}Class<?> partType = partBody.getClass();HttpHeaders partHeaders = partEntity.getHeaders();MediaType partContentType = partHeaders.getContentType();for (HttpMessageConverter<?> messageConverter : this.partConverters) {if (messageConverter.canWrite(partType, partContentType)) {Charset charset = isFilenameCharsetSet() ? StandardCharsets.US_ASCII : this.charset;HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os, charset);multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));if (!partHeaders.isEmpty()) {multipartMessage.getHeaders().putAll(partHeaders);}((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);return;}}throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +"found for request type [" + partType.getName() + "]");}/*** Generate a multipart boundary.* This implementation delegates to* MimeTypeUtils#generateMultipartBoundary().*/protected byte[] generateMultipartBoundary() {return MimeTypeUtils.generateMultipartBoundary();}/*** Return an HttpEntity for the given part Object.* @param part the part to return an HttpEntity for* @return the part Object itself it is an HttpEntity,* or a newly built HttpEntity wrapper for that part*/protected HttpEntity<?> getHttpEntity(Object part) {return (part instanceof HttpEntity ? (HttpEntity<?>) part : new HttpEntity<>(part));}/*** Return the filename of the given multipart part. This value will be used for the* Content-Disposition header.* The default implementation returns Resource#getFilename() if the part is a* Resource, and null in other cases. Can be overridden in subclasses.* @param part the part to determine the file name for* @return the filename, or null if not known*/@Nullableprotected String getFilename(Object part) {if (part instanceof Resource) {Resource resource = (Resource) part;String filename = resource.getFilename();if (filename != null && this.multipartCharset != null) {filename = MimeDelegate.encode(filename, this.multipartCharset.name());}return filename;}else {return null;}}private void writeBoundary(OutputStream os, byte[] boundary) throws IOException {os.write('-');os.write('-');os.write(boundary);writeNewLine(os);}private static void writeEnd(OutputStream os, byte[] boundary) throws IOException {os.write('-');os.write('-');os.write(boundary);os.write('-');os.write('-');writeNewLine(os);}private static void writeNewLine(OutputStream os) throws IOException {os.write('\r');os.write('\n');}/*** Implementation of org.springframework.http.HttpOutputMessage used* to write a MIME multipart.*/private static class MultipartHttpOutputMessage implements HttpOutputMessage {private final OutputStream outputStream;private final Charset charset;private final HttpHeaders headers = new HttpHeaders();private boolean headersWritten = false;public MultipartHttpOutputMessage(OutputStream outputStream, Charset charset) {this.outputStream = outputStream;this.charset = charset;}@Overridepublic HttpHeaders getHeaders() {return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);}@Overridepublic OutputStream getBody() throws IOException {writeHeaders();return this.outputStream;}private void writeHeaders() throws IOException {if (!this.headersWritten) {for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {byte[] headerName = getBytes(entry.getKey());for (String headerValueString : entry.getValue()) {byte[] headerValue = getBytes(headerValueString);this.outputStream.write(headerName);this.outputStream.write(':');this.outputStream.write(' ');this.outputStream.write(headerValue);writeNewLine(this.outputStream);}}writeNewLine(this.outputStream);this.headersWritten = true;}}private byte[] getBytes(String name) {return name.getBytes(this.charset);}}/*** Inner class to avoid a hard dependency on the JavaMail API.*/private static class MimeDelegate {public static String encode(String value, String charset) {try {return MimeUtility.encodeText(value, charset, null);}catch (UnsupportedEncodingException ex) {throw new IllegalStateException(ex);}}}}

Spring Web : FormHttpMessageConverter相关推荐

  1. Spring Web MVC(一)

    概述 Spring Web MVC框架的特点 五大核心组件 编程步骤 五大核心组件 DispatcherServlet前端控制器 WebApplicationContext中特殊的bean 处理过程 ...

  2. 用Spring Web Flow和Terracotta搭建Web应用

    什么是Spring Web Flow? Spring Web Flow是Spring Framework中的web应用组件,它提供了一种编写有状态和基于会话的web应用的简便手段.Spring Web ...

  3. 【Spring Web MVC】Spring Web MVC 注解开发环境搭建

    为什么80%的码农都做不了架构师?>>>    1.创建maven项目 创建一个名为:springwebmvc-first的maven项目 2.添加依赖包 要使用springWebM ...

  4. MESSL(maven + extjs + spring portlet mvc + spring web flow + liferay )整合架构 5

    流控制文件很简单,就是根元素是<view>,然后用<view-state>来代表一个一个的页面,用<transition>来代表从一个状态到另外一个状态的跳转,如果 ...

  5. spring_了解Spring Web应用程序体系结构:经典方法

    spring 每个开发人员必须了解两件事: 架构设计是必要的. 精美的架构图并未描述应用程序的真实架构. 真正的体系结构是从开发人员编写的代码中找到的,如果不设计应用程序的体系结构,最终将得到一个具有 ...

  6. spring初始化web_了解Spring Web初始化

    spring初始化web 几年前,我们大多数人习惯到处编写XML配置文件,甚至可以设置简单的Java EE应用程序. 如今,使用Java或Groovy来配置项目已成为首选方式–您只需要看一下Sprin ...

  7. 强烈推荐Spring Web Flow权威指南

    关于Spring Web Flow权威指南 评论 读后感:这是Spring Web Flow创始人写的书.内容是基于1.0的.此书原版出版时其实2.0已经推出了,为什么老大并没有追新呢?我猜想,书中写 ...

  8. Understanding Spring Web Application Architecture: The Classic Way--转载

    原文地址:http://www.petrikainulainen.net/software-development/design/understanding-spring-web-applicatio ...

  9. Spring Web 应用的最大败笔

    来源:http://t.cn/EM8ROEb 开发人员在使用Spring应用时非常擅长谈论依赖注入的好处.不幸的是,他们没有真正的利用到它的好处,如单一职责原则,分离关注原则.如果我们一起来看看大部分 ...

最新文章

  1. C++ 笔记(08)— 数组(数组的声明、初始化、访问及修改数组元素)
  2. python【数据结构与算法】快速幂and矩阵快速幂取模(看不懂你来打我)
  3. python使用什么注释语句和运算-Python3 注释和运算符
  4. 数据库范式解析(1NF 2NF 3NF BCNF)
  5. chrome设置微信ua_Chrome谷歌浏览器模拟微信内置浏览器的方法(电脑上)
  6. Oracle结构设计技巧(访问数据库象访问内存一样 快)
  7. Spring DI(依赖注入)
  8. 笨办法学 Python · 续 练习 24:URL 快速路由
  9. UIWebView清空本地缓存
  10. Leetcode每日一题:993.cousins-in-binary-tree(二叉树的堂兄弟节点)
  11. python单引号双引号三引号_python中的单引号、双引号和三引号有何区别
  12. 服装关键点检测算法(CNN/STN)含(4点、6点以及8点)
  13. php 随机输出字符串,如何使用PHP生成随机字符串
  14. Setting up a EDK II build environment on Windows and Linux:搭建Windows和Linux开发环境[2.2]
  15. 【图文并茂】U盘重装Win10方法教程
  16. 阿里飞天分布式操作系统
  17. 用简单易懂的话语来快速入门windows缓冲区溢出
  18. 联发科毫米波雷达解决方案芯片MT2706(Autus R10)
  19. 响应式布局的个人博客
  20. 微服务大战:Spring启动大战。 Ballerina

热门文章

  1. 工作安排(反悔贪心板子题)
  2. 如何判断你的初创企业是否有募资潜力
  3. IE的layout布局
  4. 安全浏览器无法安装?看这一篇就够了
  5. 支付宝积分兑换的扫地机器人好用_如何选购扫地机器人?
  6. 关于那封刷屏的论文致谢,这碗鸡汤我干了
  7. 云南计算机一级c类基础知识,云南省大学计算机一级C类多选题及答案.pdf
  8. Python时间戳转为北京时间
  9. java实现图片镜像翻转
  10. 【pen200-lab】10.11.1.115