先说一下出现这种情况的场景。该场景在使用jetty,tomcat作为容器时是可以正常运行的。首先是表单提交。
<form id="submitForm" action="${base}/test/aa.html" method="post"> <input type="hidden" name="userSource" value="admin"/> <input type="hidden" id="hid_modulus" name="modulus" value="$!{modulus}"/> </form>
springmvc控制器代码如下:
@RequestMapping("test") @Controller public class TestController { @RequestMapping("aa.html") public String aa(Model model, RedirectAttributes redirectAttributes) { redirectAttributes.addAttribute("a","李四"); return "redirect:bb.html"; } @RequestMapping("bb.html") public String bb(Model model, HttpServletRequest request) { String a=request.getParameter("a"); return "login"; } }
aa方法做了一次重定向,重定向时传参是中文,到进入bb方法时,获取到的string a则为乱码。
查看项目已使用了spring提供的字符集设置过滤器CharacterEncodingFilter。强制设置字符集为utf-8。却依然出现乱码问题。
没办法,只能查看springmvc的源码。跟踪源码后发现,在RedirectView类的createTargetUrl方法里,会去为重定向拼装url字符串:
protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request) throws UnsupportedEncodingException { // Prepare target URL. StringBuilder targetUrl = new StringBuilder(); if (this.contextRelative && getUrl().startsWith("/")) { // Do not apply context path to relative URLs. targetUrl.append(request.getContextPath()); } targetUrl.append(getUrl()); String enc = this.encodingScheme; if (enc == null) { enc = request.getCharacterEncoding(); } if (enc == null) { enc = WebUtils.DEFAULT_CHARACTER_ENCODING; } if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) { Map<String, String> variables = getCurrentRequestUriVariables(request); targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc); } if (isPropagateQueryProperties()) { appendCurrentQueryParams(targetUrl, request); } if (this.exposeModelAttributes) { appendQueryProperties(targetUrl, model, enc); } return targetUrl.toString(); }
debug发现,在使用post做form表单提交时,request.getCharacterEncoding()获得的是null,故导致执行到了enc = WebUtils.DEFAULT_CHARACTER_ENCODING这行代码
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
使用了ISO-8859-1,故导致重定向的url字符串里没有使用utf-8字符集,导致了乱码。
那为什么request.getCharacterEncoding()获得的是null呢。在CharacterEncodingFilter过滤器里,
@Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String encoding = getEncoding(); if (encoding != null) { if (isForceRequestEncoding() || request.getCharacterEncoding() == null) { request.setCharacterEncoding(encoding); } if (isForceResponseEncoding()) { response.setCharacterEncoding(encoding); } } filterChain.doFilter(request, response); }
该过滤器会强制使用utf-8。在debug之后,发现request.setCharacterEncoding(encoding)这行代码确实有执行到。再看request.setCharacterEncoding方法里的实现,io.undertow.servlet.spec.HttpServletRequestImpl:
@Override public void setCharacterEncoding(final String env) throws UnsupportedEncodingException { if (readStarted) { return; } try { characterEncoding = Charset.forName(env); final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalServletPathMatch().getServletChain().getManagedServlet(); final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange); if (parser != null) { parser.setCharacterEncoding(env); } } catch (UnsupportedCharsetException e) { throw new UnsupportedEncodingException(); } }
debug发现,readStarted为true。故CharacterEncodingFilter过滤器设置request.setCharacterEncoding无效。
注意,HttpServletRequestImpl是undertow容器的实现,springboot集成了undertow作为web服务器。
在HttpServletRequestImpl的private FormData parseFormData()方法上设置断点,发现在进入CharacterEncodingFilter过滤器前,undertow容器会先进入parseFormData方法,调用堆栈如下:
at io.undertow.servlet.spec.HttpServletRequestImpl.parseFormData(HttpServletRequestImpl.java:750) at io.undertow.servlet.spec.HttpServletRequestImpl.getParameter(HttpServletRequestImpl.java:636) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131) at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:285) at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:264) at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:175) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:207) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:802) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)
parseFormData()如下:
private FormData parseFormData() { if (parsedFormData == null) { if (readStarted) { return null; } final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet().getManagedServlet(); final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange); if (parser == null) { return null; } readStarted = true; try { return parsedFormData = parser.parseBlocking(); } catch (IOException e) { throw new RuntimeException(e); } } return parsedFormData; }
注意,该方法把readStarted设置为true,所以在进入CharacterEncodingFilter过滤器后,调用request.setCharacterEncoding时,其实什么都没有处理,则导致springmvc在拼装重定向url字符串时,没有使用utf-8字符集,导致出现了乱码。至此出现乱码的原因分析完毕。至于为什么容器会去调用 parseFormData(),大家可以查看调用堆栈里每一步的实现,看看是怎么一步步调用到parseFormData(),我觉得这是undertow容器的一个bug。
分析完原因,给出解决方案,这样才完整。我这里的解决方案是添加一个web过滤器,利用装饰器模式,改写request.getCharacterEncoding()方法,当该方法获得为空时,默认返回utf-8。
Utf8EncodingFilter:
package com.onlyou.superp2b; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * 默认使用utf-8字符集 * Created by cd_huang on 2017/7/25. */ public class Utf8EncodingFilter extends GenericFilterBean { private static Logger logger = LoggerFactory.getLogger(Utf8EncodingFilter.class); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { Utf8EncodingRequest request =new Utf8EncodingRequest((HttpServletRequest)servletRequest); filterChain.doFilter(request,servletResponse); } protected void initFilterBean() throws ServletException { logger.info("---加载Utf8EncodingFilter!---"); } }
Utf8EncodingRequest:
package com.onlyou.superp2b; import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * 默认使用utf-8字符集 * Created by cd_huang on 2017/7/25. */ public class Utf8EncodingRequest extends HttpServletRequestWrapper { public Utf8EncodingRequest(HttpServletRequest request) { super(request); } @Override public String getCharacterEncoding() { String characterEncoding =this.getRequest().getCharacterEncoding(); return StringUtils.isBlank(characterEncoding)?"utf-8":characterEncoding; } }
最后,测试了下,确实解决了使用undertow作为web容器带来的中文乱码的问题。
相关推荐
Java开发案例-springboot-55-undertow替换默认tomcat容器-源代码+文档.rar Java开发案例-springboot-55-undertow替换默认tomcat容器-源代码+文档.rar Java开发案例-springboot-55-undertow替换默认tomcat容器-源代码...
Undertow是最新的web容器,SpringBoot也支持使用Undertow作为内置容器。Undertow容器提供了许多独特的功能,如异步处理和高性能支持。 内置web容器的配置 SpringBoot中可以通过配置文件或Java代码来配置内置web...
嵌入式Servlet容器是SpringBoot中的重要组件,能够将Web服务器(例如Tomcat、Jetty或Undertow)嵌入到应用程序的内部运行,使得部署更为简单便捷。接下来我们将根据给定的文件内容,深入探讨SpringBoot配置嵌入式...
SpringBoot可以内嵌Tomcat、Jetty或Undertow等Web服务器,这样我们无需额外的部署步骤,可以直接通过`java -jar`命令运行SpringBoot应用。 三、起步依赖(Starter POMs) SpringBoot提供了许多起步依赖,这些依赖...
4. **内嵌式Web服务器**:SpringBoot支持内嵌Tomcat、Jetty或Undertow等Web服务器,这样你无需额外部署到外部服务器,只需运行主类即可启动应用。 5. **健康检查与Actuator**:Actuator是SpringBoot的一个模块,...
首先,我们需要了解什么是HTTP2和HTTPS,以及它们的优点和缺点,然后我们将学习如何使用Spring Boot实现Undertow服务器同时支持HTTP2和HTTPS。 什么是HTTP2? HTTP2是HTTP协议自1999年HTTP1.1发布后的首个更新,...
基于Spring Boot创建Web项目是一个广泛采用的技术实践,Spring Boot作为Spring框架的一个重要分支,旨在简化基于Spring的应用开发,它提供了一种新的编程范式,使得开发者能够更快地构建独立的、生产级别的Spring...
2. **内嵌Web服务器**:SpringBoot可以内嵌Tomcat、Jetty或Undertow等Web服务器,这样无需额外部署,就可以直接运行打包后的`.jar`文件作为服务。 3. **starter POMs**:SpringBoot提供了一系列的starter POMs,...
SpringBoot新手学习手册是针对Java开发者的指南,旨在帮助初学者快速掌握SpringBoot框架的使用。SpringBoot简化了Spring应用程序的构建过程,减少了XML配置,提供了开箱即用的功能,并内置了Tomcat等Web服务器。 1....
开发者可以仅仅通过添加相应的依赖来快速启动一个SpringBoot项目,并且其内嵌的Tomcat、Jetty或Undertow容器可以避免部署WAR包的繁琐过程。同时,SpringBoot的自动配置能够智能地根据项目中添加的jar依赖配置相关的...
Undertow是一个高性能、轻量级的Java web服务器和Servlet容器,由Red Hat开发并维护。它是JBoss AS和WildFly应用服务器的核心组件之一。在2.2.18.Final版本中,Undertow提供了最新的功能更新和性能优化。 Undertow...
2. 启动类(通常是@SpringBootApplication注解的类):作为Spring Boot应用的入口,加载配置并启动Spring容器。 3. 控制器层:处理HTTP请求,通常使用@RestController或@Controller注解定义。 4. 服务层:实现业务...
由于其内嵌了Tomcat、Jetty或者Undertow等Servlet容器,使得构建的Web项目可以独立运行,非常适合于微服务架构和单体应用的开发。Spring Boot提供的“约定优于配置”理念,也使得项目的搭建更加简单快捷。 在前端...
springboot项目高校疫情防控web系统是为响应疫情期间对校园防控工作的特殊需求而开发的一个网络应用。该系统以Spring Boot为技术核心框架,以其轻量级、快速开发和独立运行的特性,为高校提供了一个便捷高效的疫情...
Servlet容器是Web应用的核心组件,Spring Boot通常支持Tomcat, Jetty和Undertow等主流容器。 2. 安装与配置:文档提供了关于如何安装Spring Boot的详细指南,包括Java开发者通常使用的构建工具,如Maven和Gradle。...
通过SpringBoot,我们可以利用其内嵌的Tomcat、Jetty或Undertow容器来实现Web应用的部署,从而无需配置额外的Servlet容器。 其次,系统采用了MVC架构,即将应用程序分为模型(Model)、视图(View)和控制器(Controller...
1. 可独立运行的 Spring 项目:由于内嵌了 Servlet 容器(如 Tomcat、Jetty 或 Undertow),Spring Boot 应用可以直接以 jar 形式运行,无需额外的服务器部署。 2. 简化的 Maven 配置:Spring Boot 提供基础的 POM ...
使用 springboot 改造 jeesite,只保留最简单的系统配置 。 介绍 1、运行主类,登录 admin/admin ...2、砍掉了所有的冗余的东西,只保留...6、由于 boot 对 jsp 的集成问题,只能用tomcat 和jetty 启动,undertow 有问题
3. **内嵌容器(Embedded Containers)**:SpringBoot 可以内嵌Tomcat、Jetty或Undertow等Servlet容器,这意味着你可以直接运行jar文件启动Web应用,无需额外安装服务器。 4. **Actuator**:SpringBoot 提供了 ...