解决Spring Boot异常返回页面中文乱码问题

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 &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;
                }
            }
        }
复制代码

 

转自  https://blog.csdn.net/q283614346/article/details/102947320

 

 

 


 

 


 

 



 


 

posted @   猪脚踏浪  阅读(4589)  评论(1编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示