boot版本:2.6.4
这阵子做开发,用到了callback jsonp。
之前对这种方式返回的数据中文编码问题没有特别注意过,出现了乱码(想想确实啊,这个小问题应该早就注意到,惭愧惭愧)。因此,这里特别做了多个实验返回”text/html“数据,查看其编码情况,不为别的,就是为了加深记忆和理解。
首先是,咱们先只考虑在controller内部处理乱码问题,如何解决呢?
函数内部解决
response回写
首先是,直接采用response的writer回写数据
案例一
代码
public void sss(HttpServletRequest request, HttpServletResponse response) {
String sss = "北京你好!-jfQQQQ";
try {
// response.setCharacterEncoding("utf-8");
// response.setHeader("Content-Type", "text/html; charset=utf-8");
// response.setHeader("Content-Type","text/html");
PrintWriter writer = response.getWriter();
writer.write(sss.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
结果
页面上查看
响应信息查看
案例二
在案例一的基础上将文字内容进行utf8编码化再返回
代码
public void sss(HttpServletRequest request, HttpServletResponse response) {
String sss = "北京你好!-jfQQQQ";
try {
response.setCharacterEncoding("utf-8");
// response.setHeader("Content-Type", "text/html; charset=utf-8");
// response.setHeader("Content-Type","text/html");
PrintWriter writer = response.getWriter();
writer.write(sss.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
结果
页面上查看
响应信息查看
可以看到,已经不是“ ?”号了。
案例三
案例二基础上,增加返回数据类型说明。
代码
public void sss(HttpServletRequest request, HttpServletResponse response) {
String sss = "北京你好!-jfQQQQ";
try {
response.setCharacterEncoding("utf-8");
// response.setHeader("Content-Type", "text/html; charset=utf-8");
response.setHeader("Content-Type","text/html");
PrintWriter writer = response.getWriter();
writer.write(sss.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
结果
页面上查看
响应信息查看
可以看到,已经可以显示正常的中文了 。并且,springboot自动增加了“charset=utf8”字样。
所以,下边的代码与上方代码效果一致:
public void sss(HttpServletRequest request, HttpServletResponse response) {
String sss = "北京你好!-jfQQQQ";
try {
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "text/html; charset=utf-8");
// response.setHeader("Content-Type","text/html");//springboot在返回时会自动增加“charset=utf8”字样,所以效果同上方代码
PrintWriter writer = response.getWriter();
writer.write(sss.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
案例四
在拼写“content-type”字样时会容易出现拼错了的情况,response API里有直接设置响应头的办法,就很省事:
代码
public void sss(HttpServletRequest request, HttpServletResponse response) {
String sss = "北京你好!-jfQQQQ";
try {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
// response.setHeader("content-tYpE", "text/html; charset=utf-8");
// response.setHeader("Content-Type","text/html");//springboot在返回时会自动增加“charset=utf8”字样,所以效果同上方代码
PrintWriter writer = response.getWriter();
writer.write(sss.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
结果
案例五
这是一个错误的使用案例,“content-type”很容易写错,或者有时候由于理解不透彻导致拼写错误,包括在不明所以的情况下,不知道 大小写 以及 “-” 的影响。所以,值得仔细测试一下,并总结下来
代码
public void sss(HttpServletRequest request, HttpServletResponse response) {
String sss = "北京你好!-jfQQQQ";
try {
response.setCharacterEncoding("utf-8");
response.setHeader("contentType", "text/html; charset=utf-8");//contentType拼写不对,应该是Content-Type
PrintWriter writer = response.getWriter();
writer.write(sss.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
结果
页面上查看:
响应头查看
原因是“Content-Type”写成了“contentType”。
于是,我又新奇的这样写:contenttype,效果与上面一样。
但是,这样写:content-type,就正确了,查看响应头:
于是,我又这样写:content-tYpE,依旧是正确的可以的。
所以,只要把content和type之间用“-”连接,就行了,不用在乎大小写。
案例六
基于案例一,在函数上方增加produces配置
代码
这里简化代码,只保留了增加produces配置的部分:
@GetMapping(value = "/info",produces = "text/html;charset=utf-8")
结果
同案例一,没有任何效果。
因为案例一的行为是自己的response的输出,脱离了spring,
通过类比,可以发现是由于response返回的header中没有指定content-type类型和编码导致的。
Springboot的注解使用案例
ok,上面是虽然在springboot下,但是根本没有使用springboot特性(除了案例六,不过虽然写了produces,但是实际上也是没用到的)。那么使用springboot的特性也是会有这种情况的。
案例一
代码
@ResponseBody
public String findCurrentUserInfo2(HttpServletRequest request) {
String sss = "北京你好!-jfQQQQ";
return sss;
}
结果
这也说明了当前springboot上下文默认使用的是ISO-8859-1编码。所以,前面的response的使用时也是由于这个原因,由此,这里抛出一个更好的解决办法:直接设定springboot的编码。
案例二
直接返回字节数组,考虑编码为utf-8.
代码
@ResponseBody
public byte[] findCurrentUserInfo2(HttpServletRequest request) {
String sss = "北京你好!-jfQQQQ";
return sss.getBytes(StandardCharsets.UTF_8);
}
结果
这种情况下,页面展示是乱码,是由于response header中没有指定编码,但是对于获取到数据的js代码来说,是可以自行解码使用的。
案例三
就是在produces里增加了“text/html;charset=utf-8”设置
代码
@GetMapping(value = "/info",produces = "text/html;charset=utf-8")
@ResponseBody
public String findCurrentUserInfo2(HttpServletRequest request) {
String sss = "北京你好!-jfQQQQ";
return sss;//.getBytes(StandardCharsets.UTF_8);
}
结果
注意:不加上charset的设置,就是失败的:
默认用了ISO的!
函数外部解决
直接设定springboot的编码
server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=truer 然后在此基础上,实验案例若干:
response直接写
案例一
public void sss(HttpServletRequest request, HttpServletResponse response) {
String sss = "北京你好!-jfQQQQ";
try {
// response.setCharacterEncoding("utf-8");
// response.setContentType("text/html; charset=utf-8");
// response.setHeader("content-tYpE", "text/html; charset=utf-8");
// response.setHeader("Content-Type","text/html");//springboot在返回时会自动增加“charset=utf8”字样,所以效果同上方代码
PrintWriter writer = response.getWriter();
writer.write(sss.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
结果
不起作用。。。。
springboot特性
案例一
代码
@ResponseBody
public String findCurrentUserInfo2(HttpServletRequest request) {
String sss = "北京你好!-jfQQQQ";
return sss;//.getBytes(StandardCharsets.UTF_8);
}
结果
不起作用。。。
案例二
基于案例一给produces增加
produces = "text/html;"
的设置,注意,没有指定“charset=utf-8”!
结论就是依旧不行:
并且可见原因是编码为ISO了!可是明明有调试源码,utf-8的properties设定的实体类有被创建,并且值就是utf-8啊!难道没用到那个类?(org.springframework.boot.web.servlet.server.Encoding)
分析源码
结合上面的案例,得出的结论是我们配置的properties并没有起到作用,那么我们就需要找到起到作用的类和方法,spring中转换消息的类一般都是“***Convertor”,再加上我们返回的是text/html,类似于string,所以自然是找到了org.springframework.http.converter.StringHttpMessageConverter类,果不其然,里面的编码默认是ISO:
so,我们来调试一下,看看返回数据的时候是不是用到了它。
ok,确认了。
接下来,跟源码(org.springframework.http.converter.StringHttpMessageConverter中),返回数据时,会有一个判断响应头的逻辑,如果controller的函数返回时没有满足if中判断的条件:
那么,就执行默认的响应头(调用到了org.springframework.http.converter.AbstractHttpMessageConverter中的addDefaultHeaders方法),如下图:
同时,由于我只配置了produces为“text/html”,所以上面的断点处,就走到了获取
his.getDefaultCharset();
这一句,要去获取默认的charset,我们看看它要去从哪里获取:
还是org.springframework.http.converter.AbstractHttpMessageConverter中,它自己存储了默认的编码值:
这个defaultCharset是在创建时指定的,截图时是utf-8。
不过这个属性是在创建时指定的,我们看看这个属性是在哪里赋值的。
赋值是在创建时指定的(org.springframework.http.converter.AbstractHttpMessageConverter):
上面的创建是在下图被调用的
然后上图是在下图被调用的
然后下图就是上图用到的默认编码值,于是走到迷宫尽头了,可以出宫了。
结论
所以,关键问题就在于我们让springboot启动初始化时,将类的默认编码换为utf-8就行了,而那个类是StringHttpMessageConverter 。
所以,我们要这样自创建一个bean,设定它的类别,再注入到spring上下文中,替换其默认的bean。
这样:
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}