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