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;
}
}
}