Spring boot版本:2.1.3
现象
异常返回页面中文有乱码,可以看到后台返回的字符编号是ISO-8859-1
后台的异常信息没有乱码
java.io.FileNotFoundException: D:\workspace\hqh\mybatis-demo\data\account.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method) ~[na:1.8.0_144]
at java.io.FileInputStream.open(FileInputStream.java:195) ~[na:1.8.0_144]
解决方法
第一种:在application.properties增加以下配置
# 解决返回页面中文乱码问题
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8(默认就是UTF-8,可以不设置)
第二种:自定义HandlerExceptionResolver,然后手工设置编码
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
response.setCharacterEncoding("utf-8");
return null;
}
}
第一种方法的原理:
配置HttpProperties默认的spring.http.encoding.charset=UTF-8,CharacterEncodingFilter注册时会设置,然后在doFilterInternal方法中,如果配置spring.http.encoding.force设为true,则把请求和响应的编码设置为spring.http.encoding.charset的值,源码如下
HttpProperties.java,位置:org.springframework.boot.autoconfigure.http.HttpProperties
/**
* Configuration properties for http encoding.
*/
public static class Encoding {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
HttpEncodingAutoConfiguration.java,位置:org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal()方法源码
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 取字符编码
String encoding = getEncoding();
if (encoding != null) {
// 如果spring.http.encoding.force设为true且请求编码为空,则设置请求编码
if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
// 如果spring.http.encoding.force设为true,则设置响应编码
if (isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
如果上面的spring.http.encoding.force不为true,则下面会使用默认的ISO_8859_1,springboot在映射视图时org.springframework.web.servlet.View.render()调用了org.springframework.web.util.HtmlUtils.htmlEscape(String)转换错误信息的时候使用的是默认编码ISO_8859_1,如下源码
WebUtils.java
/**
* Default character encoding to use when {@code request.getCharacterEncoding}
* returns {@code null}, according to the Servlet spec.
* @see ServletRequest#getCharacterEncoding
*/
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
HtmlUtils.java
/**
* Turn special characters into HTML character references.
* Handles complete character set defined in HTML 4.01 recommendation.
* <p>Escapes all special characters to their corresponding
* entity reference (e.g. {@code <}).
* <p>Reference:
* <a href="http://www.w3.org/TR/html4/sgml/entities.html">
* http://www.w3.org/TR/html4/sgml/entities.html
* </a>
* @param input the (unescaped) input string
* @return the escaped string
*/
public static String htmlEscape(String input) {
return htmlEscape(input, WebUtils.DEFAULT_CHARACTER_ENCODING);
}
然后在org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.StaticView.render方法最后一句获取输出流时还有一个默认编码ISO-8859-1
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (response.isCommitted()) {
String message = getMessage(model);
logger.error(message);
return;
}
StringBuilder builder = new StringBuilder();
Date timestamp = (Date) model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>").append(timestamp).append("</div>")
.append("<div>There was an unexpected error (type=")
.append(htmlEscape(model.get("error"))).append(", status=")
.append(htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>")
.append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
// 取Writer时还有一个默认编码ISO-8859-1
response.getWriter().append(builder.toString());
}
response.getWriter()方法的源码,可以看到是调用response.getWriter()取的
// 位置:org.apache.catalina.connector.ResponseFacade.getWriter()
@Override
public PrintWriter getWriter()
throws IOException {
// if (isFinished())
// throw new IllegalStateException
// (/*sm.getString("responseFacade.finished")*/);
PrintWriter writer = response.getWriter();
if (isFinished()) {
response.setSuspended(true);
}
return writer;
}
response.getWriter()的源码,ENFORCE_ENCODING_IN_GET_WRITER默认为true
// 位置:org.apache.catalina.connector.Response.getWriter()
private static final boolean ENFORCE_ENCODING_IN_GET_WRITER;
static {
ENFORCE_ENCODING_IN_GET_WRITER = Boolean.parseBoolean(
System.getProperty("org.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER",
"true"));
}
@Override
public PrintWriter getWriter()
throws IOException {
if (usingOutputStream) {
throw new IllegalStateException
(sm.getString("coyoteResponse.getWriter.ise"));
}
if (ENFORCE_ENCODING_IN_GET_WRITER) {
/*
* If the response's character encoding has not been specified as
* described in <code>getCharacterEncoding</code> (i.e., the method
* just returns the default value <code>ISO-8859-1</code>),
* <code>getWriter</code> updates it to <code>ISO-8859-1</code>
* (with the effect that a subsequent call to getContentType() will
* include a charset=ISO-8859-1 component which will also be
* reflected in the Content-Type response header, thereby satisfying
* the Servlet spec requirement that containers must communicate the
* character encoding used for the servlet response's writer to the
* client).
*/
setCharacterEncoding(getCharacterEncoding());
}
usingWriter = true;
outputBuffer.checkConverter();
if (writer == null) {
writer = new CoyoteWriter(outputBuffer);
}
return writer;
}
/**
* @return the character encoding used for this Response.
*/
@Override
public String getCharacterEncoding() {
String charset = getCoyoteResponse().getCharacterEncoding();
if (charset != null) {
return charset;
}
Context context = getContext();
String result = null;
if (context != null) {
result = context.getResponseCharacterEncoding();
}
// 默认编码为ISO-8859-1
if (result == null) {
result = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET.name();
}
return result;
}
第二种方法的原理:会在org.springframework.web.servlet.DispatcherServlet.processHandlerException()中调用自定义的HandlerExceptionResolver设置编码,源码如下
// 部分源码
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}