实验项目SpringBoot
版本为2.3.5.RELEASE
如若担忧其余版本是否适用本方案,请查看文章 兼容性 章节html
来到这里的朋友,你必定碰见了中文参数乱码的问题。前端
你是否有如下症状:java
server.servlet.encoding.charset=utf-8
、server.servlet.encoding.force=true
仍是会有乱码server.undertow.urlCharset=utf-8
仍是会有乱码咱们先来搭建一下现场以便还原事故web
pom.xml 配置以下便可:ajax
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <!-- 省略部分项目属性 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.5.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> </dependencies> </project>
springboot启动类:spring
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class); } }
请求controller,注意启动类没有添加@ComponentScan
注解,要把controller放到启动类同目录或者下一级目录下,这个功能是返回给定的姓名apache
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController public class DemoController { @RequestMapping("/a") @CrossOrigin public String echoName(HttpServletRequest request) { String name = request.getParameter("name"); System.out.println(name); //打印一下,debug的话能够不用打印也能看到 return name; } }
至此搭建完毕,启动项目。后端
咦,竟然是正常的浏览器
post请求html页面代码tomcat
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <form method="post" action="http://127.0.0.1:8080/a"> <input type="text" name="name" value="张三" /> <input type="submit" /> </form> </body> </html>
请求结果:
咦,竟然也是正常的
ajax请求源码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <script> var xhr = new XMLHttpRequest(); xhr.open('post', 'http://127.0.0.1:8080/a' ); xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //发送请求参数 xhr.send('name=张三'); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; </script> </body> </html>
请求结果:
这里是乱码了
能够看到,借用浏览器的普通get\post请求,都能返回正常无乱码结果。
也许你的浏览器返回的是乱码结果,不过这是合理的。
这里说一下为何能返回正常结果:得益于如今浏览器的智能行为,它对你的参数进行了隐式的URL编码转换。
F12打开浏览器控制台,看get的原报文:
蓝色选中Method上方,是请求的url,能够看到参数 name=张三
已经被隐式地转码了。同理,post也是,上述post请求浏览器控制台最后一行已经很清晰地显示了转码后的参数。
这一点,对于新手或者不熟悉前端知识的后端开发人员来讲,很容易让人解决乱码的时候无从下手。
这也就明白了,为何ajax请求返回的,是乱码,由于它的参数没有获得浏览器的URL编码转换。这是咱们指望的,也就是上述说的乱码结果是合理的。
也许有些人会说,那我只要在ajax请求里对参数转码就行了,这里给出一个建议:
尽可能对全部参数进行URL转码,除非你很清楚它只有字母和数字
浏览器也是人创造的,万一哪一天,它再也不偷偷给你转码了呢?
咱们debug去查看应用后台的参数接受状况,参数接受代码这样:
String name = request.getParameter("name");
debug得知,当request中不存在key为name
的参数时候,会从容器中获取字节,而后进行form表单数据的转换。
其中undertow对http请求字节的转换处理,在io.undertow.server.handlers.form.FormEncodedDataDefinition.doParse()
方法中:
private void doParse(final StreamSourceChannel channel) throws IOException { //省略部分代码 final ByteBuffer buffer = pooled.getBuffer(); //省略部分代码 byte n = buffer.get(); //省略部分代码 builder.append((char) n); //关键操做,对字节直接强转为char,这也就是接受到参数为乱码的缘由 //省略部分代码 addPair(name, builder.toString()); //把转换后的参数存储起来,最终会存放到request中 //省略部分代码 }
由上述代码得知,undertow对咱们的参数直接进行了强制char型转换,而不是由字节转到字符串,致使request中获取到的参数为强转后乱码的缘由。并且undertow官方认为这不是个错误,拒绝修复。泱泱大国,遭受歧视,努力奋斗吧骚年,让我大中华民族在科技界再也不遭受忽略、排挤、打击的日子早点到来。
就没有别的办法了吗?
办法仍是有的。能够看到,byte直接转成了char,没有中间操做,不存在高低补位的状况,数据精度并无丢失,咱们再转换回来,便可获得原始的byte字节,而后在转换成字符串,这才是咱们想要的。
有以下验证:
public class DecoderTest { public static void main(String[] args) { String name = "¥ᄐᅠ¦ᄌノ"; char[] chars = name.toCharArray(); byte[] bytes = new byte[chars.length]; for(int i = 0; i < chars.length; i++){ bytes[i] = (byte) chars[i]; } System.out.println(new String(bytes)); } }
控制台输出结果为:张三
想法可行,那么,咱们只要添加拦截器,在业务功能获取参数前,反转后存放到request中,就能够了。
添加以下拦截器:
import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Enumeration; public class UndertowRevertInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()){ String name = parameterNames.nextElement(); String value = request.getParameter(name); value = revert(value); //注意不要和原有key重复,我不是教你写bug,只是提供一种思路。 request.setAttribute(name,value); } return true; } private String revert(String s){ char[] chars = s.toCharArray(); byte[] bytes = new byte[chars.length]; for(int i = 0; i < chars.length; i++){ bytes[i] = (byte) chars[i]; } //如系统非使用UTF-8编码,请替换为带有编码格式的构造函数 return new String(bytes); } }
注意是放到了 attribute
里面,request
不提供setParameter
方法,想一想也是合理的,http单次请求原本就是单向发送到后端的,setParameter
作什么?
把拦截器注入到Spring当中:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //注意把参数转换拦截器放到第一位,若是有多个拦截器,在其下面添加 registry.addInterceptor(new UndertowRevertInterceptor()); } }
controller里面获取参数相应替换成:
String name = (String) request.getAttribute("name");
重启应用,获得正确的值,博主还拿了锟斤拷
去测试:
固然除了拦截器,还能够有以下方法:
HandlerMethodArgumentResolver
OncePerRequestFilter
,参考 CharacterEncodingFilter
的实现。aop
,拦截点能够有不少,太麻烦,不建议。你们有什么精巧的办法和别的想法,欢迎留言。
博主因时间缘由,并无充分测试,只要undertow在参数转换的时候依然是由byte
强转为char
,本方法就会生效。