解決Spring Boot異常返回頁面中文亂碼問題

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 &lt;}).
	 * <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;
				}
			}
		}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章