一、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调用时,传入data编码有误导致,通过以下方式解决(红色字体部分):
前台ajax调用代码:
$.ajax({
二:spring mvc通过@ResponseBody返回时,ajax接收到乱码结果:
修改spring mvc controller代码(添加红色部分),指定响应头编码方式:
。。。。
}
方法二、修改全局的默认配置编码
<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