解决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 <}). * <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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!