问题
当controller的接口返回字符串,在swaggerui中测试时,发现返回都是问号,比如”?????id 100 ???????“,这是由于字符编码问题导致
例如:
responseentity.status(httpstatus.not_found) .body(string.format("未找到相应id %d 的记录", id));
网上解决方案
现有的两种解决方案:
- 第一种,针对单独接口,在requestmapping里设置 produces = {"text/plain;charset=utf-8"}
- 第二种,统一在mvc配置类中,通过修改stringhttpmessageconverter默认配置,部分代码(ps,该代码从别处拷贝而来):
@configuration @enablewebmvc public class mymvcconfig implements webmvcconfigurer { @bean public httpmessageconverter<string> responsebodystringconverter() { stringhttpmessageconverter converter = new stringhttpmessageconverter(standardcharsets.utf_8); return converter; } @override public void configuremessageconverters(list<httpmessageconverter<?>> converters){ converters.add(responsebodystringconverter()); } }
是由于默认的编码是”standardcharsets.iso_8859_1“导致,是通过重写”configuremessageconverters“方法来设置utf-8编码来解决。
也就是第二种,坑了我,也许是我使用不当?
新解决方案
通过研究源码,找到了新的解决思路:
因为通过重写”configuremessageconverters“方法后,会导致一些其他问题
比如,统一处理异常的exceptionadvicehandler不工作,还导致controller接口不支持文件下载
比如:
//解决中文文件名的乱码问题 string utf8 = standardcharsets.utf_8.name(); try { downloadfilename = urlencoder.encode(downloadfilename, utf8); } catch (unsupportedencodingexception e) { // } return responseentity.ok() .contenttype(mediatype.application_octet_stream) .header(httpheaders.content_disposition, "attachment; filename* = " + utf8 + "''" + downloadfilename) .body(new urlresource(downloadfile.touri()));
并且调用下载接口时,会报406错误和异常”no converter for [class org.springframework.core.io.urlresource]”,意思是不支持 “application/octet-stream“的转换,见鬼了,通过测试,禁用掉webmvcconfigurer的重写,下载功能就ok了,但是会重新有编码问题。
最终通过研究源码,找到了根源,这是由于设置了自己的converter导致默认的其他converters不会再被初始化添加导致,参见webmvcconfigurationsupport的代码:
protected final list<httpmessageconverter<?>> getmessageconverters() { if (this.messageconverters == null) { this.messageconverters = new arraylist(); this.configuremessageconverters(this.messageconverters); if (this.messageconverters.isempty()) { this.adddefaulthttpmessageconverters(this.messageconverters); } this.extendmessageconverters(this.messageconverters); } return this.messageconverters; }
所以基于这个代码,我们则应该重写extendmessageconverters方法来达到目的,最终的代码是:
@bean public httpmessageconverter<string> responsebodystringconverter() { stringhttpmessageconverter converter = new stringhttpmessageconverter(standardcharsets.utf_8); return converter; } @override public void extendmessageconverters(list<httpmessageconverter<?>> converters) { list<stringhttpmessageconverter> stringhttpmessageconverters = converters.stream() .filter(converter -> converter.getclass().equals(stringhttpmessageconverter.class)) .map(converter -> (stringhttpmessageconverter) converter) .collect(collectors.tolist()); if (stringhttpmessageconverters.isempty()) { converters.add(responsebodystringconverter()); } else { stringhttpmessageconverters.foreach(converter -> converter.setdefaultcharset(standardcharsets.utf_8)); } }
json格式的编码探讨
这里仅处理接口直接返回字符串的问题,而对于处理json返回,这是因为json返回由mappingjackson2httpmessageconverter来控制:
protected jsonencoding getjsonencoding(@nullable mediatype contenttype) { if (contenttype != null && contenttype.getcharset() != null) { charset charset = contenttype.getcharset(); for (jsonencoding encoding : jsonencoding.values()) { if (charset.name().equals(encoding.getjavaname())) { return encoding; } } } return jsonencoding.utf8; }
所以对于返回json对象,无需处理,且已经提供了默认的utf-8编码,因为当默认没有设置mediatype的编码格式时,则会使用该默认的utf-8编码。
并且mediatype中针对json的编码有如下解释:
/** * a string equivalent of {@link mediatype#application_json_utf8}. * @deprecated as of 5.2 in favor of {@link #application_json_value} * since major browsers like chrome * <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464" rel="external nofollow" > * now comply with the specification</a> and interpret correctly utf-8 special * characters without requiring a {@code charset=utf-8} parameter. */ @deprecated public static final string application_json_utf8_value = "application/json;charset=utf-8";
ps:org.springframework.boot:spring-boot-starter-web:jar:2.2.1.release
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论