springmvc中级-HttpMessageConverter详解

一、HttpMessageConverter概述

HttpMessageConverter<T> 是 Spring3.0 新添加的一个接 口,负责将请求信息转换为一个对象(类型为 T),将对象( 类型为 T)输出为响应信息。


使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入 参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
–使用 @RequestBody / @ResponseBody 对处理方法进行标注
–使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的入参或返回值

当控制器处理方法使用到 @RequestBody/@ResponseBody 或HttpEntity<T>/ResponseEntity<T> 时, Spring 首先根据请求头或响应头的Accept 属性选择匹配的 HttpMessageConverter,  进而根据参数类型或 泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的HttpMessageConverter 将报错

  • @RequestBody@ResponseBody 不需要成对出现。

ajax发送和接收乱码问题:

因为HttpMessageConverter的几个实现类,都是先查询http请求头的编码方式,然后再解码的,如果没有指定就使用默认的编码方式。比如iso-8859-1。这样会使得中文乱码。

​碰到使用ajax调用spring mvc时出现乱码,这里写一下解决方法。​

一:ajax传入spring mvc时,@RequestBody出现乱码:

这是ajax调用时,传入data编码有误导致,通过以下方式解决(红色字体部分):

前台ajax调用代码:

$.ajax({
  url : baseUrl + “/crlandIsp/commonInfoCreate/save.do”,
  type : “POST”,
  cache : false,
  data : mdata,
  contentType : “text/html;charset=UTF-8”,
  dataType : “json”,
  success : function(result) {

  },
  error : function(xhr, ajaxOptions, thrownError) {
  

  }
 });

二:spring mvc通过@ResponseBody返回时,ajax接收到乱码结果:

修改spring mvc controller代码(添加红色部分),指定响应头编码方式

 @RequestMapping(value = “/queryDetail”,produces = “text/html;charset=UTF-8”)
   public @ResponseBody String save(@RequestBody String body) throws Exception {
       try {

。。。。

}

方法二、修改全局的默认配置编码

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

网上也有这种,但是你们是不是<mvc:annotation-driven>不能配置子集
这里只要把你们的spring-mvc-.xsd改成4就可以在<mvc:annotation-driven>下配置了,如下图

主要还是spring-mvc-4.0.xsd。改了之后再eclipse中按ALT+/就会出现如下提示

这边还要注意一点

<property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />

text/plain这个还是会中文乱码,用text/html就不会


二、HttpMessageConverter应用

1、测试请求体信息

(1)测试带上传文件

utf-8编码的文件:春节.txt

good春节OK
hello

文件二进制为:

676f 6f64 e698 a5e8 8a82 4f4b 0d0a 6865
6c6c 6f

(2)测试网页:

<form action="testRequestBody" method="post" enctype="multipart/form-data" accept-charset="gb2312">
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

(2)测试请求体的控制器:

@ResponseBody
@RequestMapping("/testRequestBody")
public String testHttpMessageConverter(@RequestBody String body){
	System.out.println(body);
	return "helloworld! " + new Date();
}

浏览器fiddler2抓包结果:
(将二进制流用gb2312解码的结果,很明显文件上传时传的是原始二进制流,原始信息是utf-8编码,上传时没有在重新经过编码,于是春节,二字的utf-8编码是 E6 98 A5 E8 8A 82 的二进制流,抓包解码时,将E6 98 A5 E8 8A 82用GB2312解码,于是出现乱码   鏄ヨ妭    ,至于表单中的其他信息 ,因为都采用了gb2312编码,所以解码时就正确了,不会出现乱码 )

POST http://192.168.1.131:8080/springmvc-2/testRequestBody HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 410
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrvTDZPfQOBKllDXv
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=5E065281AC2C661A0098111FB38F3745

------WebKitFormBoundaryrvTDZPfQOBKllDXv
Content-Disposition: form-data; name="name-file"; filename="春节.txt"
Content-Type: text/plain

good鏄ヨ妭OK
hello
------WebKitFormBoundaryrvTDZPfQOBKllDXv
Content-Disposition: form-data; name="name-desc"

春节
------WebKitFormBoundaryrvTDZPfQOBKllDXv
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundaryrvTDZPfQOBKllDXv--

服务器控制台输出结果:【输出的是请求体】

------WebKitFormBoundaryrvTDZPfQOBKllDXv
Content-Disposition: form-data; name="name-file"; filename="´º½Ú.txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundaryrvTDZPfQOBKllDXv
Content-Disposition: form-data; name="name-desc"

´º½Ú
------WebKitFormBoundaryrvTDZPfQOBKllDXv
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundaryrvTDZPfQOBKllDXv--

特别备注:控制台返回的请求体中:

##### 针对 filename="´º½Ú.txt" ,
##### 这个 ´º½Ú 是浏览器 用gb2312编码 春节 两字成 B4 BA BD DA 的二进制流,放入请求体中
##### 解码时 springmvc 查看http头,发现没有编码指明,于是默认用 iso-8859-1解码 造成乱码   
Content-Disposition: form-data; name="name-file"; filename="´º½Ú.txt"
##### 下面第三行 也是同样的 原因
Content-Disposition: form-data; name="name-desc"

´º½Ú
##### 总结来说,multipart/form-data 编码的表单,表单中信息都是按 表单规定的编码方式,
##### 直接编码成 二进制流。然后存放到 请求体中。而表单其他自动生成的信息,都是ASCII编码
##### 其实也可以说是按照 表单默认 的编码方式,因为http 用到的编码都是 ASCII码的扩展码,都兼容支持ASCII码
##### 当然 文件上传时 没有进行编码,直接是原始文件的二进制流加入到 请求体中 比如下面的文件信息:
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundaryrvTDZPfQOBKllDXv
##### 上面的例子中:原始文件信息 已经说过了 是用utf-8编码 成二进制流,utf-8兼容ASCII码,
##### 当 springmvc 接收请求体时,因为发现请求头中没有编码信息 ,所以采用默认的iso-8859-1解码,这个兼容ASCII码
##### 所以  英文字母能够正常解码 ,中文 春节 二字,一开始utf-8编码成 E6 98 A5 E8 8A 82 的二进制流,
##### 后来解码时 ,用ios-8859-1将这些二进制流错误的解码了,导致出现乱码  春节

=========================================================

如果将表单设置成utf-8编码,效果就不一样了。

<form action="testRequestBody" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

用utf-8解码抓包时的二进制流:

POST http://192.168.1.131:8080/springmvc-2/testRequestBody HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 414
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySIE2A85QOHmfv0rl
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=69D0B1B2F9AAE721AED15F660424C708

------WebKitFormBoundarySIE2A85QOHmfv0rl
Content-Disposition: form-data; name="name-file"; filename="春节.txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundarySIE2A85QOHmfv0rl
Content-Disposition: form-data; name="name-desc"

春节
------WebKitFormBoundarySIE2A85QOHmfv0rl
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundarySIE2A85QOHmfv0rl--

查看服务器控制台输出结果:
所有乱码  春节  都是因为春节的utf-8 编码成二进制流  E6 98 A5 E8 8A 82
后来用iso-8859-1 解码 E6 98 A5 E8 8A 82 时,出现乱码   æ˜¥èŠ‚

------WebKitFormBoundarySIE2A85QOHmfv0rl
Content-Disposition: form-data; name="name-file"; filename="春节.txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundarySIE2A85QOHmfv0rl
Content-Disposition: form-data; name="name-desc"

春节
------WebKitFormBoundarySIE2A85QOHmfv0rl
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundarySIE2A85QOHmfv0rl--

为什么,控制台打印出来的是乱码呢?

debug解析过程:

核心靠AbstractHttpMessageConverter.java 解析body
解码时,首先看请求头的编码,如果没有,则默认iso-8859-1编码。

//类方法 有删减
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

	public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");


	private volatile List<Charset> availableCharsets;

	private boolean writeAcceptCharset = true;


	/**
	 * A default constructor that uses {@code "ISO-8859-1"} as the default charset.
	 * @see #StringHttpMessageConverter(Charset)
	 */
	public StringHttpMessageConverter() {
		this(DEFAULT_CHARSET);
	}

	/**
	 * A constructor accepting a default charset to use if the requested content
	 * type does not specify one.
	 */
	public StringHttpMessageConverter(Charset defaultCharset) {
		super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
	}


	/**
	 * Indicates whether the {@code Accept-Charset} should be written to any outgoing request.
	 * <p>Default is {@code true}.
	 */
	public void setWriteAcceptCharset(boolean writeAcceptCharset) {
		this.writeAcceptCharset = writeAcceptCharset;
	}



	@Override
	protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
		Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
		return StreamUtils.copyToString(inputMessage.getBody(), charset);
	}



	@Override
	protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
		if (this.writeAcceptCharset) {
			outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
		}
		Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
		StreamUtils.copy(str, charset, outputMessage.getBody());
	}




}

所以,解决方法也好办了:

1.客户的发送方法稍微改一下,这个方式是最好的:

connection.setRequestProperty("content-type", "text/html;charset=GBk");

2.如果发送方无论如何都没法改,只能服务器端处理的时候,要么所有接收到的数据都 new String(data.getByte(“iso-8859-1″),”GBK”),要么直接自定义StringHttpConverter,将readInternal方法中的charset获取改为不先从请求头中读取,直接硬编码为GBK(当然这样做的后果就是只能接收GBK的数据了).【好像不能自定义,不过上文提到了可以转发配置中配置,修改StringHttpConverter的默认编码值】

2、文件下载

只需要在控制器中,编辑 ResponseEntity<byte[]>就行了。

@RequestMapping("/testResponseEntity")
	public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
		byte [] body = null;
		ServletContext servletContext = session.getServletContext();
		InputStream in = servletContext.getResourceAsStream("/files/abc.txt");
		body = new byte[in.available()];
		in.read(body);
		
		HttpHeaders headers = new HttpHeaders();
		headers.add("Content-Disposition", "attachment;filename=abc.txt");
		
		HttpStatus statusCode = HttpStatus.OK;
		
		ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body, headers, statusCode);
		return response;
	}

 

3、返回json

添加jackson jar包,编写控制器。【如果有中文的json,】

@ResponseBody
@RequestMapping("/testJson")
public Collection<Employee> testJson(){
	return employeeDao.getAll();
}

测试链接:http://localhost:8080/springmvc-2/testJson

 

参考:

测试 request body 问题

https://www.cnblogs.com/chyu/p/5517788.html

https://blog.csdn.net/u013239111/article/details/51347949

http://blog.sina.com.cn/s/blog_b1f34f990102vcm4.html

 

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments