Spring boot版本:2.1.3
異常返回頁面中文有亂碼,可以看到后台返回的字符編號是ISO-8859-1
但是后台輸出正常。
解決辦法一:
在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; } } }
轉自 https://blog.csdn.net/q283614346/article/details/102947320