乱码问题的根源
要解决乱码,首先要明白它为什么会发生,乱码的根本原因在于:编码和解码使用的字符集不一致。
想象一下,你有一封用中文写的信(信息),你需要把它打包寄出去(编码/序列化),对方收到后需要拆开阅读(解码/反序列化)。
- 编码:将字符(如 '中')转换成计算机可以存储和传输的字节流(如
[-28, -72, -83])。 - 解码:将字节流(
[-28, -72, -83])重新还原成字符('中')。
如果在打包时你用的是 GBK 字符集,而对方拆包时误以为是 UTF-8 字符集,那么就会出现乱码,Servlet 乱码问题就发生在这个“打包”和“拆包”的过程中。
在 Web 开发中,主要涉及三个环节的编码:
- 浏览器端(客户端)编码:用户在浏览器中输入数据时,浏览器使用什么编码格式将数据发送给服务器。
- 服务器端(Tomcat)解码:Tomcat 接收到请求后,使用什么编码格式来解析请求数据(如 POST 请求体)。
- 服务器端(Tomcat)编码:Tomcat 将响应数据发送回浏览器前,使用什么编码格式将数据转换成字节流。
- 浏览器端(客户端)解码:浏览器接收到响应数据后,使用什么编码格式来解析并显示。
只要这四个环节中,任意一对“编码”和“解码”所用的字符集不一致,就可能产生乱码。
常见乱码场景及解决方案
我们根据不同的请求和响应类型来分别讨论解决方案。
GET 请求乱码
GET 请求的参数是附加在 URL 后面的,http://localhost:8080/test?name=张三&age=25。
-
乱码原因:
- 浏览器将
张三这个字符串,根据当前页面的编码(通常是 UTF-8)进行 URL 编码,变成%E5%BC%A0%E4%B8%89。 - 这个请求发送到 Tomcat 服务器,Tomcat 在接收到 URL 后,默认使用
ISO-8859-1字符集来解码 URL 中的%E5%BC%A0%E4%B8%89。 - 由于
ISO-8859-1字符集中不存在这些字节对应的中文,所以解码失败,变成了乱码。
- 浏览器将
-
解决方案: 核心思路:将 Tomcat 默认的
ISO-8859-1解码后的字节数组,再用正确的编码(UTF-8)重新编码一次。// 在 Servlet 的 doGet 或 doPost 方法中 String name = request.getParameter("name"); // 方案一:手动转换(适用于少量参数) // 1. 用 ISO-8859-1 解码成字节数组 byte[] bytes = name.getBytes("ISO-8859-1"); // 2. 用 UTF-8 重新编码成字符串 name = new String(bytes, "UTF-8"); // 方案二:一劳永逸的通用方法(推荐) // 写一个工具方法,在所有需要处理 GET 请求的地方调用 request.setCharacterEncoding("UTF-8"); // 这对 GET 请求无效,但养成好习惯 String name2 = new String(request.getParameter("name").getBytes("ISO-8859-1"), "UTF-8"); -
更优的解决方案(修改 Tomcat 配置): 如果你的项目大量使用 GET 请求,每次手动转换很麻烦,可以修改 Tomcat 的配置文件,让它默认使用 UTF-8 解码 URL。
- 打开 Tomcat 安装目录下的
conf/server.xml文件。 - 找到
<Connector>标签,在其中添加URIEncoding="UTF-8"属性。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" /> <!-- 添加这一行 -->修改后重启 Tomcat,所有 GET 请求的乱码问题就都解决了。
- 打开 Tomcat 安装目录下的
POST 请求乱码
POST 请求的参数在请求体中,不是附加在 URL 后面。
-
乱码原因:
- 浏览器将表单数据用 UTF-8 编码后发送给 Tomcat。
- Tomcat 接收到数据后,默认使用
ISO-8859-1字符集来解析请求体,导致乱码。
-
解决方案: 核心思路:在获取请求参数之前,明确告诉 Tomcat 使用 UTF-8 字符集来解析请求体。
// 在 Servlet 的 doGet 或 doPost 方法中,第一行代码就执行 request.setCharacterEncoding("UTF-8"); // 然后再获取参数 String username = request.getParameter("username"); String password = request.getParameter("password"); // ...注意:
request.setCharacterEncoding("UTF-8")必须在request.getParameter()之前调用,否则无效。
响应乱码(服务器 -> 浏览器)
服务器处理完业务逻辑后,需要将数据(如 HTML 页面)返回给浏览器显示。
-
乱码原因:
- Tomcat 将你的响应内容(如
<h1>你好,世界</h1>)按照默认的ISO-8859-1字符集转换成字节流发送给浏览器。 - 浏览器接收到字节流后,根据页面声明的编码(或默认的编码)来解码,如果页面声明的是 UTF-8,而服务器发送的是
ISO-8859-1编码的字节流,浏览器自然无法正确显示,导致乱码。
- Tomcat 将你的响应内容(如
-
解决方案: 核心思路:告诉 Tomcat 用 UTF-8 编码,同时告诉浏览器也用 UTF-8 解码。
// 在 Servlet 的 doGet 或 doPost 方法中,第一行代码就执行 response.setContentType("text/html;charset=UTF-8"); // 然后获取输出流并写入内容 PrintWriter out = response.getWriter(); out.println("<h1>你好,世界!</h1>"); out.println("<p>欢迎来到我的网站。</p>");response.setContentType("text/html;charset=UTF-8")这行代码做了两件事:- 设置响应内容的 MIME 类型为
text/html。 - 设置响应内容的字符编码为
UTF-8,Tomcat 会据此将字符串转换成字节流。 - 它会向响应头中添加一个
Content-Type头信息,Content-Type: text/html; charset=UTF-8,浏览器看到这个头信息,就知道应该用 UTF-8 来解码这个页面。
- 设置响应内容的 MIME 类型为
终极解决方案:统一编码过滤器
对于任何一个稍微复杂一点的 Web 应用,在每个 Servlet 的开头都写上两行编码设置代码是非常繁琐且容易出错的,我们使用 过滤器 来统一处理所有的请求和响应乱码问题。
这是一个标准且一劳永逸的做法。
创建一个编码过滤器 EncodingFilter
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
// @WebFilter("/*") 表示拦截所有请求
@WebFilter("/*")
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
public void init(FilterConfig filterConfig) throws ServletException {
// 可以从 web.xml 中读取配置的编码
String encodingFromConfig = filterConfig.getInitParameter("encoding");
if (encodingFromConfig != null) {
this.encoding = encodingFromConfig;
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. 处理请求乱码
request.setCharacterEncoding(encoding);
// 2. 处理响应乱码
response.setContentType("text/html;charset=" + encoding);
// 3. 放行,让请求继续走到目标 Servlet
chain.doFilter(request, response);
}
public void destroy() {
// Filter 销毁时调用,一般无需处理
}
}
配置 web.xml(可选,但推荐)
虽然我们用了 @WebFilter 注解,但为了更灵活地配置,也可以在 web.xml 中配置过滤器。
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.yourpackage.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
工作原理:
- 当任何一个请求进入服务器时,首先会被
EncodingFilter拦截。 request.setCharacterEncoding("UTF-8")会设置后续request.getParameter()解析 POST 请求体时使用的编码。response.setContentType("text/html;charset=UTF-8")会设置 Tomcat 发送响应时使用的编码,并告诉浏览器用 UTF-8 解码。- 对于 GET 请求,Tomcat 的
server.xml中已经配置了URIEncoding="UTF-8",那么就不会乱码,如果没有,过滤器无法直接修复 GET 请求乱码,强烈建议同时配置 Tomcat 的URIEncoding。
总结与最佳实践
| 场景 | 问题原因 | 解决方案 | 推荐指数 |
|---|---|---|---|
| POST 请求 | Tomcat 默认用 ISO-8859-1 解析请求体 |
request.setCharacterEncoding("UTF-8"); |
⭐⭐⭐ |
| 响应乱码 | Tomcat 默认用 ISO-8859-1 编码响应,浏览器可能用 UTF-8 解码 |
response.setContentType("text/html;charset=UTF-8"); |
⭐⭐⭐ |
| GET 请求 | Tomcat 默认用 ISO-8859-1 解码 URL |
修改 Tomcat server.xml (URIEncoding="UTF-8") |
⭐⭐⭐⭐⭐ |
| 统一处理 | 手动在每个 Servlet 中设置编码太繁琐 | 使用 EncodingFilter 统一处理 |
⭐⭐⭐⭐⭐ |
最佳实践组合拳:
- 修改 Tomcat 全局配置:在
conf/server.xml的<Connector>标签中添加URIEncoding="UTF-8",这解决了所有 GET 请求的乱码问题。 - 创建并配置
EncodingFilter:创建一个EncodingFilter,并配置拦截所有请求 (),这解决了所有 POST 请求和响应的乱码问题。
完成这两步后,你的整个 Web 应用就拥有了统一的、可靠的 UTF-8 编码环境,从此告别乱码烦恼。
