Servlet中文乱码问题解决方法:从请求到响应的全流程编码设置与常见陷阱排查
引言:理解Servlet中文乱码问题的本质
在Java Web开发中,Servlet中文乱码问题是一个经典且常见的技术难题。这个问题通常表现为:从浏览器提交的中文数据在服务器端显示为乱码,或者服务器返回的中文内容在浏览器中显示为”???“或乱码字符。要彻底解决这个问题,我们需要理解字符编码的基本原理,并掌握从请求到响应的全流程编码设置方法。
字符编码是计算机系统中用于表示字符的二进制规则。在Web应用中,数据需要在客户端(浏览器)和服务器(Servlet容器)之间传输,如果两端使用的编码不一致,就会导致乱码。UTF-8是目前最推荐的编码方式,它支持全球所有字符,且具有良好的兼容性。
请求处理阶段的编码设置
GET请求的编码处理
GET请求的参数通常直接附加在URL中,例如:http://localhost:8080/app?name=张三。在Servlet中获取这些参数时,需要特别注意编码问题。
问题分析:
- 默认情况下,Tomcat等Servlet容器对GET请求使用ISO-8859-1编码
- 当URL中包含中文时,浏览器会使用UTF-8对中文进行URL编码
- 如果服务器端不进行正确解码,就会得到乱码
解决方案:
方法一:在Tomcat配置中设置URIEncoding(推荐) 在Tomcat的
conf/server.xml文件中,找到Connector配置,添加URIEncoding="UTF-8"属性:<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />方法二:在Servlet中手动解码
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取原始参数值(可能乱码) String rawName = request.getParameter("name"); // 使用ISO-8859-1重新编码为字节,再用UTF-8解码 String correctName = new String(rawName.getBytes("ISO-8859-1"), "UTF-8"); // 或者使用URLDecoder(适用于URL编码的参数) String correctName2 = URLDecoder.decode(request.getParameter("name"), "UTF-8"); }
POST请求的编码处理
POST请求的参数放在请求体中,需要在读取参数之前设置请求体的编码。
问题分析:
- 默认情况下,POST请求体使用ISO-8859-1编码
- 必须在调用
request.getParameter()之前设置编码 - 一旦参数被读取,编码设置将无效
解决方案:
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 必须在读取任何参数之前设置编码 request.setCharacterEncoding("UTF-8"); // 现在可以安全地获取中文参数 String username = request.getParameter("username"); String comment = request.getParameter("comment"); // 处理业务逻辑... } 重要注意事项:
setCharacterEncoding()必须在所有getParameter()调用之前执行- 该方法只对POST请求体有效,对GET请求无效
- 如果请求已经通过
getReader()或getInputStream()读取过,则编码设置无效
响应阶段的编码设置
HTTP响应头的编码设置
服务器向浏览器返回数据时,需要通过HTTP响应头告诉浏览器使用什么编码来显示内容。
解决方案:
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型和编码 response.setContentType("text/html; charset=UTF-8"); // 或者分开设置 // response.setContentType("text/html"); // response.setCharacterEncoding("UTF-8"); // 获取输出流并写入中文内容 PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>欢迎," + request.getParameter("name") + "!</h1>"); out.println("</body></html>"); } 字符输出流的编码处理
当使用response.getWriter()时,Servlet容器会根据设置的编码将字符转换为字节流。
完整示例:
@WebServlet("/encodingDemo") public class EncodingDemoServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 请求编码设置 request.setCharacterEncoding("UTF-8"); // 2. 响应编码设置 response.setContentType("text/html; charset=UTF-8"); response.setCharacterEncoding("UTF-8"); // 3. 获取参数 String name = request.getParameter("name"); String email = request.getParameter("email"); // 4. 构建响应内容 response.getWriter().println("<!DOCTYPE html>"); response.getWriter().println("<html>"); response.getWriter().println("<head>"); response.getWriter().println("<meta charset="UTF-8">"); response.getWriter().println("<title>用户信息确认</title>"); response.getWriter().println("</head>"); response.getWriter().println("<body>"); response.getWriter().println("<h2>用户注册信息</h2>"); response.getWriter().println("<p>姓名:" + name + "</p>"); response.getWriter().println("<p>邮箱:" + email + "</p>"); response.getWriter().println("</body>"); response.getWriter().println("</html>"); } } 过滤器统一处理编码(推荐方案)
为了避免在每个Servlet中重复设置编码,可以使用过滤器进行统一处理。
过滤器实现代码
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 统一编码过滤器 * 拦截所有请求,统一设置请求和响应的编码为UTF-8 */ @WebFilter("/*") public class EncodingFilter implements Filter { private static final String ENCODING = "UTF-8"; @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化配置(可选) } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 设置请求编码(仅对POST有效) if (httpRequest.getMethod().equalsIgnoreCase("POST")) { httpRequest.setCharacterEncoding(ENCODING); } // 设置响应编码 httpResponse.setContentType("text/html; charset=" + ENCODING); httpResponse.setCharacterEncoding(ENCODING); // 继续执行后续过滤器和Servlet chain.doFilter(request, response); } @Override public void destroy() { // 清理资源 } } 增强版过滤器(支持GET请求)
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 增强版编码过滤器 * 支持GET和POST请求的编码处理 */ public class EnhancedEncodingFilter implements Filter { private static final String ENCODING = "UTF-8"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 设置请求编码 httpRequest.setCharacterEncoding(ENCODING); // 设置响应编码 httpResponse.setContentType("text/html; charset=" + ENCODING); httpResponse.setCharacterEncoding(ENCODING); // 包装请求对象以支持GET请求编码(可选方案) if ("GET".equalsIgnoreCase(httpRequest.getMethod())) { // 使用包装器处理GET请求参数 EncodingRequestWrapper wrapper = new EncodingRequestWrapper(httpRequest, ENCODING); chain.doFilter(wrapper, response); } else { chain.doFilter(request, response); } } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} } /** * 请求包装器,用于处理GET请求参数编码 */ class EncodingRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper { private final String encoding; public EncodingRequestWrapper(HttpServletRequest request, String encoding) { super(request); this.encoding = encoding; } @Override public String getParameter(String name) { String value = super.getParameter(name); if (value == null) return null; try { return new String(value.getBytes("ISO-8859-1"), encoding); } catch (Exception e) { return value; } } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> map = super.getParameterMap(); // 处理map中的所有值... return map; } } 常见陷阱与排查方法
陷阱1:过滤器顺序问题
问题描述: 如果项目中有多个过滤器,编码过滤器必须放在最前面,否则其他过滤器可能已经读取了请求参数。
解决方案: 在web.xml中配置过滤器的<dispatcher>顺序,或使用注解的@WebFilter配合@Order(Servlet 3.0+)。
<filter> <filter-name>encodingFilter</filter-name> <filter-class>com.example.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 其他过滤器配置在后面 --> <filter> <filter-name>authFilter</filter-name> <filter-class>com.example.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>authFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 陷阱2:JSP页面编码不一致
问题描述: 即使Servlet设置了编码,如果JSP页面本身编码不一致,仍会出现乱码。
解决方案: 在JSP页面顶部设置page指令:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 或者在JSP中设置响应头:
<% request.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=UTF-8"); %> 陷阱3:数据库连接编码问题
问题描述: 数据在Servlet中正常,但存入数据库后变成乱码,或从数据库读出时乱码。
解决方案: 在JDBC连接字符串中指定编码:
String url = "jdbc:mysql://localhost:3306/dbname?useUnicode=true&characterEncoding=UTF-8"; Connection conn = DriverManager.getConnection(url, username, password); 陷阱4:文件上传时的编码问题
问题描述: 使用multipart/form-data格式上传文件时,普通参数获取不到或乱码。
解决方案: 使用Apache Commons FileUpload或Servlet 3.0+的Part API:
@WebServlet("/upload") @MultipartConfig public class UploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取文本参数(需要手动解析) String description = null; Part filePart = request.getPart("file"); for (Part part : request.getParts()) { if (part.getName().equals("description")) { // 从part的header中获取filename,判断是文本还是文件 String contentDisposition = part.getHeader("content-disposition"); if (!contentDisposition.contains("filename")) { // 这是文本参数 description = new String(part.getInputStream().readAllBytes(), "UTF-8"); } } } } } 陷阱5:AJAX请求编码问题
问题描述: AJAX请求发送中文数据时出现乱码。
解决方案: 确保AJAX请求设置正确的content-type:
// jQuery示例 $.ajax({ url: '/your-servlet', type: 'POST', contentType: 'application/x-www-form-urlencoded; charset=UTF-8', data: { name: '张三', comment: '中文评论' }, success: function(response) { console.log(response); } }); // 原生fetch示例 fetch('/your-servlet', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: new URLSearchParams({ name: '张三', comment: '中文评论' }) }); 完整的最佳实践示例
项目结构配置
web.xml配置(Servlet 2.5⁄3.0):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- 编码过滤器 --> <filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.example.filter.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> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping> <!-- 其他过滤器... --> <!-- Servlet配置 --> <servlet> <servlet-name>UserServlet</servlet-name> <servlet-class>com.example.UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UserServlet</servlet-name> <url-pattern>/user</url-pattern> </servlet-mapping> </web-app> 完整的Servlet示例
package com.example.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 用户管理Servlet - 完整编码处理示例 */ @WebServlet("/user") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 即使有编码过滤器,这里也可以再次确认 response.setContentType("text/html; charset=UTF-8"); String action = request.getParameter("action"); if ("register".equals(action)) { showRegistrationForm(request, response); } else { processUserRequest(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 确保编码(如果过滤器已设置,这里可省略) request.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=UTF-8"); String action = request.getParameter("action"); if ("register".equals(action)) { handleRegistration(request, response); } } private void showRegistrationForm(HttpServletRequest request, HttpServletResponse response) throws IOException { PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset="UTF-8">"); out.println("<title>用户注册</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>用户注册表单</h2>"); out.println("<form method="POST" action="/user?action=register">"); out.println(" 姓名:<input type="text" name="username" required><br>"); out.println(" 邮箱:<input type="email" name="email" required><br>"); out.println(" 备注:<textarea name="comment"></textarea><br>"); out.println(" <input type="submit" value="注册">"); out.println("</form>"); out.println("</body>"); out.println("</html>"); } private void handleRegistration(HttpServletRequest request, HttpServletResponse response) throws IOException { String username = request.getParameter("username"); String email = request.getParameter("email"); String comment = request.getParameter("comment"); // 模拟保存到数据库(实际项目中应使用PreparedStatement) // 这里仅演示编码处理 PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset="UTF-8">"); out.println("<title>注册成功</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>注册信息确认</h2>"); out.println("<p><strong>用户名:</strong>" + username + "</p>"); out.println("<p><strong>邮箱:</strong>" + email + "</p>"); out.println("<p><strong>备注:</strong>" + comment + "</p>"); out.println("<p><a href="/user?action=register">返回注册</a></p>"); out.println("</body>"); out.println("</html>"); } private void processUserRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { // 处理其他用户请求... response.getWriter().println("其他用户操作页面..."); } } 调试与排查工具
1. 使用浏览器开发者工具
- Network面板:查看请求的Content-Type和实际发送的数据
- Console面板:检查AJAX响应数据
- Headers面板:确认响应头中的charset设置
2. 服务器日志调试
在Servlet中添加调试日志:
import java.util.logging.Logger; public class DebugServlet extends HttpServlet { private static final Logger logger = Logger.getLogger(DebugServlet.class.getName()); @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 记录原始请求信息 logger.info("Request Character Encoding: " + request.getCharacterEncoding()); logger.info("Content Type: " + request.getContentType()); logger.info("Content Length: " + request.getContentLength()); // 读取原始请求体(用于调试) BufferedReader reader = request.getReader(); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } logger.info("Raw Request Body: " + sb.toString()); // 设置编码后重新读取 request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); logger.info("Decoded Username: " + username); } } 3. 字节级调试
// 检查字符串的字节表示 String testStr = "测试"; System.out.println("UTF-8 bytes: " + Arrays.toString(testStr.getBytes("UTF-8"))); System.out.println("ISO-8859-1 bytes: " + Arrays.toString(testStr.getBytes("ISO-8859-1"))); // 检查乱码转换过程 String wrong = new String(testStr.getBytes("UTF-8"), "ISO-8859-1"); System.out.println("Wrong encoding: " + wrong); // 乱码 String correct = new String(wrong.getBytes("ISO-8859-1"), "UTF-8"); System.out.println("Corrected: " + correct); // 恢复正常 总结与检查清单
必须检查的项目
请求阶段:
- [ ] POST请求:
request.setCharacterEncoding("UTF-8")在所有getParameter()之前 - [ ] GET请求:Tomcat的
URIEncoding="UTF-8"配置 - [ ] 过滤器:编码过滤器放在最前面
- [ ] POST请求:
响应阶段:
- [ ]
response.setContentType("text/html; charset=UTF-8") - [ ]
response.setCharacterEncoding("UTF-8") - [ ] JSP页面:
<%@ page contentType="text/html; charset=UTF-8" %> - [ ] HTML页面:
<meta charset="UTF-8">
- [ ]
数据库阶段:
- [ ] JDBC连接字符串包含
characterEncoding=UTF-8 - [ ] 数据库表和字段字符集为utf8mb4
- [ ] JDBC连接字符串包含
文件编码:
- [ ] Java源文件保存为UTF-8
- [ ] 配置文件(properties)使用UTF-8
- [ ] IDE统一设置为UTF-8
推荐的全局配置
Tomcat server.xml:
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true" ... />全局过滤器(web.xml或注解):
@WebFilter(urlPatterns = "/*", dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})JSP全局设置: 在
web.xml中配置JSP默认编码:<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> </jsp-property-group> </jsp-config>
通过以上全面的设置和检查,可以彻底解决Servlet中的中文乱码问题。记住,预防胜于治疗,在项目初期就建立统一的编码规范是最有效的方法。# Servlet中文乱码问题解决方法:从请求到响应的全流程编码设置与常见陷阱排查
引言:理解Servlet中文乱码问题的本质
在Java Web开发中,Servlet中文乱码问题是一个经典且常见的技术难题。这个问题通常表现为:从浏览器提交的中文数据在服务器端显示为乱码,或者服务器返回的中文内容在浏览器中显示为”???“或乱码字符。要彻底解决这个问题,我们需要理解字符编码的基本原理,并掌握从请求到响应的全流程编码设置方法。
字符编码是计算机系统中用于表示字符的二进制规则。在Web应用中,数据需要在客户端(浏览器)和服务器(Servlet容器)之间传输,如果两端使用的编码不一致,就会导致乱码。UTF-8是目前最推荐的编码方式,它支持全球所有字符,且具有良好的兼容性。
请求处理阶段的编码设置
GET请求的编码处理
GET请求的参数通常直接附加在URL中,例如:http://localhost:8080/app?name=张三。在Servlet中获取这些参数时,需要特别注意编码问题。
问题分析:
- 默认情况下,Tomcat等Servlet容器对GET请求使用ISO-8859-1编码
- 当URL中包含中文时,浏览器会使用UTF-8对中文进行URL编码
- 如果服务器端不进行正确解码,就会得到乱码
解决方案:
方法一:在Tomcat配置中设置URIEncoding(推荐) 在Tomcat的
conf/server.xml文件中,找到Connector配置,添加URIEncoding="UTF-8"属性:<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />方法二:在Servlet中手动解码
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取原始参数值(可能乱码) String rawName = request.getParameter("name"); // 使用ISO-8859-1重新编码为字节,再用UTF-8解码 String correctName = new String(rawName.getBytes("ISO-8859-1"), "UTF-8"); // 或者使用URLDecoder(适用于URL编码的参数) String correctName2 = URLDecoder.decode(request.getParameter("name"), "UTF-8"); }
POST请求的编码处理
POST请求的参数放在请求体中,需要在读取参数之前设置请求体的编码。
问题分析:
- 默认情况下,POST请求体使用ISO-8859-1编码
- 必须在调用
request.getParameter()之前设置编码 - 一旦参数被读取,编码设置将无效
解决方案:
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 必须在读取任何参数之前设置编码 request.setCharacterEncoding("UTF-8"); // 现在可以安全地获取中文参数 String username = request.getParameter("username"); String comment = request.getParameter("comment"); // 处理业务逻辑... } 重要注意事项:
setCharacterEncoding()必须在所有getParameter()调用之前执行- 该方法只对POST请求体有效,对GET请求无效
- 如果请求已经通过
getReader()或getInputStream()读取过,则编码设置无效
响应阶段的编码设置
HTTP响应头的编码设置
服务器向浏览器返回数据时,需要通过HTTP响应头告诉浏览器使用什么编码来显示内容。
解决方案:
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型和编码 response.setContentType("text/html; charset=UTF-8"); // 或者分开设置 // response.setContentType("text/html"); // response.setCharacterEncoding("UTF-8"); // 获取输出流并写入中文内容 PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>欢迎," + request.getParameter("name") + "!</h1>"); out.println("</body></html>"); } 字符输出流的编码处理
当使用response.getWriter()时,Servlet容器会根据设置的编码将字符转换为字节流。
完整示例:
@WebServlet("/encodingDemo") public class EncodingDemoServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 请求编码设置 request.setCharacterEncoding("UTF-8"); // 2. 响应编码设置 response.setContentType("text/html; charset=UTF-8"); response.setCharacterEncoding("UTF-8"); // 3. 获取参数 String name = request.getParameter("name"); String email = request.getParameter("email"); // 4. 构建响应内容 response.getWriter().println("<!DOCTYPE html>"); response.getWriter().println("<html>"); response.getWriter().println("<head>"); response.getWriter().println("<meta charset="UTF-8">"); response.getWriter().println("<title>用户信息确认</title>"); response.getWriter().println("</head>"); response.getWriter().println("<body>"); response.getWriter().println("<h2>用户注册信息</h2>"); response.getWriter().println("<p>姓名:" + name + "</p>"); response.getWriter().println("<p>邮箱:" + email + "</p>"); response.getWriter().println("</body>"); response.getWriter().println("</html>"); } } 过滤器统一处理编码(推荐方案)
为了避免在每个Servlet中重复设置编码,可以使用过滤器进行统一处理。
过滤器实现代码
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 统一编码过滤器 * 拦截所有请求,统一设置请求和响应的编码为UTF-8 */ @WebFilter("/*") public class EncodingFilter implements Filter { private static final String ENCODING = "UTF-8"; @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化配置(可选) } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 设置请求编码(仅对POST有效) if (httpRequest.getMethod().equalsIgnoreCase("POST")) { httpRequest.setCharacterEncoding(ENCODING); } // 设置响应编码 httpResponse.setContentType("text/html; charset=" + ENCODING); httpResponse.setCharacterEncoding(ENCODING); // 继续执行后续过滤器和Servlet chain.doFilter(request, response); } @Override public void destroy() { // 清理资源 } } 增强版过滤器(支持GET请求)
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 增强版编码过滤器 * 支持GET和POST请求的编码处理 */ public class EnhancedEncodingFilter implements Filter { private static final String ENCODING = "UTF-8"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 设置请求编码 httpRequest.setCharacterEncoding(ENCODING); // 设置响应编码 httpResponse.setContentType("text/html; charset=" + ENCODING); httpResponse.setCharacterEncoding(ENCODING); // 包装请求对象以支持GET请求编码(可选方案) if ("GET".equalsIgnoreCase(httpRequest.getMethod())) { // 使用包装器处理GET请求参数 EncodingRequestWrapper wrapper = new EncodingRequestWrapper(httpRequest, ENCODING); chain.doFilter(wrapper, response); } else { chain.doFilter(request, response); } } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} } /** * 请求包装器,用于处理GET请求参数编码 */ class EncodingRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper { private final String encoding; public EncodingRequestWrapper(HttpServletRequest request, String encoding) { super(request); this.encoding = encoding; } @Override public String getParameter(String name) { String value = super.getParameter(name); if (value == null) return null; try { return new String(value.getBytes("ISO-8859-1"), encoding); } catch (Exception e) { return value; } } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> map = super.getParameterMap(); // 处理map中的所有值... return map; } } 常见陷阱与排查方法
陷阱1:过滤器顺序问题
问题描述: 如果项目中有多个过滤器,编码过滤器必须放在最前面,否则其他过滤器可能已经读取了请求参数。
解决方案: 在web.xml中配置过滤器的<dispatcher>顺序,或使用注解的@WebFilter配合@Order(Servlet 3.0+)。
<filter> <filter-name>encodingFilter</filter-name> <filter-class>com.example.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 其他过滤器配置在后面 --> <filter> <filter-name>authFilter</filter-name> <filter-class>com.example.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>authFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 陷阱2:JSP页面编码不一致
问题描述: 即使Servlet设置了编码,如果JSP页面本身编码不一致,仍会出现乱码。
解决方案: 在JSP页面顶部设置page指令:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 或者在JSP中设置响应头:
<% request.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=UTF-8"); %> 陷阱3:数据库连接编码问题
问题描述: 数据在Servlet中正常,但存入数据库后变成乱码,或从数据库读出时乱码。
解决方案: 在JDBC连接字符串中指定编码:
String url = "jdbc:mysql://localhost:3306/dbname?useUnicode=true&characterEncoding=UTF-8"; Connection conn = DriverManager.getConnection(url, username, password); 陷阱4:文件上传时的编码问题
问题描述: 使用multipart/form-data格式上传文件时,普通参数获取不到或乱码。
解决方案: 使用Apache Commons FileUpload或Servlet 3.0+的Part API:
@WebServlet("/upload") @MultipartConfig public class UploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取文本参数(需要手动解析) String description = null; Part filePart = request.getPart("file"); for (Part part : request.getParts()) { if (part.getName().equals("description")) { // 从part的header中获取filename,判断是文本还是文件 String contentDisposition = part.getHeader("content-disposition"); if (!contentDisposition.contains("filename")) { // 这是文本参数 description = new String(part.getInputStream().readAllBytes(), "UTF-8"); } } } } } 陷阱5:AJAX请求编码问题
问题描述: AJAX请求发送中文数据时出现乱码。
解决方案: 确保AJAX请求设置正确的content-type:
// jQuery示例 $.ajax({ url: '/your-servlet', type: 'POST', contentType: 'application/x-www-form-urlencoded; charset=UTF-8', data: { name: '张三', comment: '中文评论' }, success: function(response) { console.log(response); } }); // 原生fetch示例 fetch('/your-servlet', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: new URLSearchParams({ name: '张三', comment: '中文评论' }) }); 完整的最佳实践示例
项目结构配置
web.xml配置(Servlet 2.5⁄3.0):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- 编码过滤器 --> <filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.example.filter.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> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping> <!-- 其他过滤器... --> <!-- Servlet配置 --> <servlet> <servlet-name>UserServlet</servlet-name> <servlet-class>com.example.UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UserServlet</servlet-name> <url-pattern>/user</url-pattern> </servlet-mapping> </web-app> 完整的Servlet示例
package com.example.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 用户管理Servlet - 完整编码处理示例 */ @WebServlet("/user") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 即使有编码过滤器,这里也可以再次确认 response.setContentType("text/html; charset=UTF-8"); String action = request.getParameter("action"); if ("register".equals(action)) { showRegistrationForm(request, response); } else { processUserRequest(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 确保编码(如果过滤器已设置,这里可省略) request.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=UTF-8"); String action = request.getParameter("action"); if ("register".equals(action)) { handleRegistration(request, response); } } private void showRegistrationForm(HttpServletRequest request, HttpServletResponse response) throws IOException { PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset="UTF-8">"); out.println("<title>用户注册</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>用户注册表单</h2>"); out.println("<form method="POST" action="/user?action=register">"); out.println(" 姓名:<input type="text" name="username" required><br>"); out.println(" 邮箱:<input type="email" name="email" required><br>"); out.println(" 备注:<textarea name="comment"></textarea><br>"); out.println(" <input type="submit" value="注册">"); out.println("</form>"); out.println("</body>"); out.println("</html>"); } private void handleRegistration(HttpServletRequest request, HttpServletResponse response) throws IOException { String username = request.getParameter("username"); String email = request.getParameter("email"); String comment = request.getParameter("comment"); // 模拟保存到数据库(实际项目中应使用PreparedStatement) // 这里仅演示编码处理 PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset="UTF-8">"); out.println("<title>注册成功</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>注册信息确认</h2>"); out.println("<p><strong>用户名:</strong>" + username + "</p>"); out.println("<p><strong>邮箱:</strong>" + email + "</p>"); out.println("<p><strong>备注:</strong>" + comment + "</p>"); out.println("<p><a href="/user?action=register">返回注册</a></p>"); out.println("</body>"); out.println("</html>"); } private void processUserRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { // 处理其他用户请求... response.getWriter().println("其他用户操作页面..."); } } 调试与排查工具
1. 使用浏览器开发者工具
- Network面板:查看请求的Content-Type和实际发送的数据
- Console面板:检查AJAX响应数据
- Headers面板:确认响应头中的charset设置
2. 服务器日志调试
在Servlet中添加调试日志:
import java.util.logging.Logger; public class DebugServlet extends HttpServlet { private static final Logger logger = Logger.getLogger(DebugServlet.class.getName()); @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 记录原始请求信息 logger.info("Request Character Encoding: " + request.getCharacterEncoding()); logger.info("Content Type: " + request.getContentType()); logger.info("Content Length: " + request.getContentLength()); // 读取原始请求体(用于调试) BufferedReader reader = request.getReader(); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } logger.info("Raw Request Body: " + sb.toString()); // 设置编码后重新读取 request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); logger.info("Decoded Username: " + username); } } 3. 字节级调试
// 检查字符串的字节表示 String testStr = "测试"; System.out.println("UTF-8 bytes: " + Arrays.toString(testStr.getBytes("UTF-8"))); System.out.println("ISO-8859-1 bytes: " + Arrays.toString(testStr.getBytes("ISO-8859-1"))); // 检查乱码转换过程 String wrong = new String(testStr.getBytes("UTF-8"), "ISO-8859-1"); System.out.println("Wrong encoding: " + wrong); // 乱码 String correct = new String(wrong.getBytes("ISO-8859-1"), "UTF-8"); System.out.println("Corrected: " + correct); // 恢复正常 总结与检查清单
必须检查的项目
请求阶段:
- [ ] POST请求:
request.setCharacterEncoding("UTF-8")在所有getParameter()之前 - [ ] GET请求:Tomcat的
URIEncoding="UTF-8"配置 - [ ] 过滤器:编码过滤器放在最前面
- [ ] POST请求:
响应阶段:
- [ ]
response.setContentType("text/html; charset=UTF-8") - [ ]
response.setCharacterEncoding("UTF-8") - [ ] JSP页面:
<%@ page contentType="text/html; charset=UTF-8" %> - [ ] HTML页面:
<meta charset="UTF-8">
- [ ]
数据库阶段:
- [ ] JDBC连接字符串包含
characterEncoding=UTF-8 - [ ] 数据库表和字段字符集为utf8mb4
- [ ] JDBC连接字符串包含
文件编码:
- [ ] Java源文件保存为UTF-8
- [ ] 配置文件(properties)使用UTF-8
- [ ] IDE统一设置为UTF-8
推荐的全局配置
Tomcat server.xml:
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true" ... />全局过滤器(web.xml或注解):
@WebFilter(urlPatterns = "/*", dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})JSP全局设置: 在
web.xml中配置JSP默认编码:<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> </jsp-property-group> </jsp-config>
通过以上全面的设置和检查,可以彻底解决Servlet中的中文乱码问题。记住,预防胜于治疗,在项目初期就建立统一的编码规范是最有效的方法。
支付宝扫一扫
微信扫一扫