首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Web 中文亂碼終極解決方案:三層編碼統一與最佳實踐

     編輯:Java 相關知識

前言

中文亂碼是 Java Web 開發者的「成人禮」。本質上是 **字符編碼不一致** 導致的字節流解碼錯誤。本文從 HTTP 協議層面劃分 **請求解碼、業務處理、響應編碼** 三大環節,提供 **Filter 統一過濾、容器配置、代碼規範** 三級解決方案,並深度解析 Tomcat 8/9/10、Spring Boot、Nginx 反向代理場景下的配置細節,徹底告別亂碼。

一、亂碼根因:HTTP 請求/響應編碼鏈路

瀏覽器編碼 (UTF-8/GBK) 
    → HTTP 請求體/URL/Header (字節流) 
    → Tomcat Connector 解碼 (request.setCharacterEncoding / URIEncoding) 
    → Servlet request.getParameter() (字符串) 
    → Java 內存 (UTF-16 char[]) 
    → 業務邏輯 / 數據庫 (JDBC URL characterEncoding) 
    → 響應字符串 
    → Servlet response.setContentType / setCharacterEncoding 
    → Tomcat 編碼輸出 
    → 瀏覽器解碼渲染 (Content-Type charset)

核心原則:全鏈路統一 UTF-8。任何一環不一致即產生亂碼。

二、方案一:Filter 統一編碼過濾器 (最通用、推薦)

在 `web.xml` 或 `@WebFilter` 中配置,**必須是第一個 Filter** (或 Spring Boot `Ordered.HIGHEST_PRECEDENCE`),在讀取請求參數前執行。

通用編碼 Filter 代碼 (支援 GET/POST、Multipart)

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

@WebFilter(urlPatterns = "/*", dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})
public class EncodingFilter implements Filter {
    private static final String ENCODING = "UTF-8";
    private static final String CONTENT_TYPE = "text/html;charset=" + ENCODING;

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        // 1. 設置響應編碼 (最早執行,防止提交響應頭)
        response.setCharacterEncoding(ENCODING);
        response.setContentType(CONTENT_TYPE);

        // 2. 處理請求編碼
        // POST: request.setCharacterEncoding 有效
        // GET: 參數在 URL 查詢字符串,Tomcat 8+ 默認 ISO-8859-1 解碼,需 Wrapper 重新解碼
        if ("POST".equalsIgnoreCase(request.getMethod())) {
            request.setCharacterEncoding(ENCODING);
            chain.doFilter(request, response);
        } else {
            // GET 請求包裝器:重新解碼 queryString
            chain.doFilter(new HttpServletRequestWrapper(request) {
                @Override
                public String getParameter(String name) {
                    String value = super.getParameter(name);
                    if (value == null) return null;
                    try {
                        // Tomcat 8+ 已按 ISO-8859-1 解碼為字符串,需轉回字節再按 UTF-8 解碼
                        return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
                    } catch (UnsupportedEncodingException e) {
                        return value; // 降級
                    }
                }
                @Override
                public String[] getParameterValues(String name) {
                    String[] values = super.getParameterValues(name);
                    if (values == null) return null;
                    for (int i = 0; i < values.length; i++) {
                        try {
                            values[i] = URLDecoder.decode(values[i], StandardCharsets.UTF_8.name());
                        } catch (UnsupportedEncodingException ignored) {}
                    }
                    return values;
                }
            }, response);
        }
    }
}

三、方案二:Tomcat 容器級配置 (一勞永逸,無代碼侵入)

1. POST 請求體解碼 (server.xml / Context.xml)

<!-- server.xml Connector 配置 -->
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="UTF-8" 
           useBodyEncodingForURI="true" />
  • URIEncoding="UTF-8":強制 GET 請求 URL 查詢字符串按 UTF-8 解碼 (Tomcat 8+ 默認已為 UTF-8,但顯式配置更安全)。
  • useBodyEncodingForURI="true":若請求體編碼已設置 (如 Filter 中 `request.setCharacterEncoding`),則用該編碼解碼 URL 參數 (兼容舊應用)。

2. 全局請求/響應編碼 (web.xml / Spring Boot 配置)

<!-- web.xml (Servlet 3.0+) -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- Spring 提供 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value> <!-- 強制覆蓋 request/response 編碼 -->
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Spring Boot 2.x/3.x 自動配置 (application.yml)

server:
  servlet:
    encoding:
      charset: UTF-8
      force: true
      enabled: true
  tomcat:
    uri-encoding: UTF-8
    # use-body-encoding-for-uri: true # 可選
# Spring Boot 會自動注冊 CharacterEncodingFilter (Ordered.HIGHEST_PRECEDENCE)

四、方案三:代碼規範與特殊場景處理

1. 數據庫連接編碼 (JDBC URL)

# MySQL 8.0+ (默認 utf8mb4)
jdbc:mysql://localhost:3306/db?characterEncoding=utf8&useUnicode=true&serverTimezone=Asia/Shanghai
# 8.0+ 無需 characterEncoding,默認 utf8mb4,但顯式無害

# PostgreSQL
jdbc:postgresql://localhost:5432/db?charSet=utf-8

# Oracle (需確保數據庫字符集 AL32UTF8)
jdbc:oracle:thin:@localhost:1521:orcl

2. 文件上傳 (Multipart) 編碼

// Spring Boot
spring:
  servlet:
    multipart:
      enabled: true
      # file-size-threshold: 0 # 內存閾值
      # max-file-size: 10MB
      # max-request-size: 100MB
# Standard Servlet MultipartConfigElement 設置
@MultipartConfig(location="/tmp", fileSizeThreshold=1024*1024)
public class UploadServlet extends HttpServlet { ... }

**關鍵**:上傳組件 (Commons FileUpload / Servlet 3.0 Part) 需設置 `setHeaderEncoding("UTF-8")` 解析文件名/字段名。

3. 頁面模板與靜態資源

  • JSP: `<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>`
  • Thymeleaf: `spring.thymeleaf.encoding=UTF-8` (默認)
  • HTML: `` 放在 `` 首行。
  • JS/CSS 文件保存為 UTF-8 無 BOM。

4. Nginx 反向代理傳遞編碼

location / {
    proxy_pass https://backend;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    # 關鍵:透傳編碼相關 Header,防止 Tomcat 誤判
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Accept-Encoding ""; # 禁用 gzip 讓後端處理 (可選)
}

五、GET/POST 亂碼差異深度解析

維度POSTGET
數據位置請求體URL 查詢字符串
Tomcat 默認解碼ISO-8859-1 (需 `request.setCharacterEncoding` 覆蓋)ISO-8859-1 (Tomcat 7) / UTF-8 (Tomcat 8+ 默認)
Filter 生效點`request.setCharacterEncoding()` 有效無效!需 Wrapper 重新解碼或 Tomcat `URIEncoding`
最佳實踐Filter 統一設置Tomcat `URIEncoding="UTF-8"` + Filter Wrapper 雙保險

六、常見避坑檢查清單

  • ✅ 所有 Java 源文件保存為 **UTF-8 無 BOM** (IDE 設置)。
  • ✅ 數據庫表/列字符集 **utf8mb4** (支援 Emoji)。
  • ✅ JDBC URL 顯式 `characterEncoding=utf8` (MySQL 5.7) 或依賴 8.0+ 默認。
  • ✅ Filter **順序最前** (Spring Boot `Ordered.HIGHEST_PRECEDENCE`)。
  • ✅ `forceEncoding=true` 強制覆蓋響應編碼。
  • ✅ Tomcat `URIEncoding="UTF-8"` 顯式配置。
  • ✅ 前端 `` 與 `Content-Type` Header 一致。
  • ✅ 文件上傳組件設置 Header 編碼。
  • ❌ 手動 `new String(bytes, "ISO-8859-1")` 又 `getBytes("UTF-8")` 雙重轉碼 (除非極其確定)。
  • ❌ 依賴 `request.getParameter()` 前未設置編碼。

七、診斷工具:Arthas 現場排查

$ arthas
[arthas@12345]$ watch org.apache.catalina.connector.Request setCharacterEncoding '{params, returnObj, throwExp}' -x 2
[arthas@12345]$ trace org.springframework.web.filter.CharacterEncodingFilter doFilter
[arthas@12345]$ getstatic org.apache.catalina.connector.CoyoteRequest URIEncoding
// 實時查看編碼設置是否生效、請求參數解碼後的真實值

總結

Java Web 中文亂碼的本質是 **編碼協議不一致**。解決之道是建立 **全鏈路 UTF-8 標準**:

  1. 容器層:Tomcat `URIEncoding="UTF-8"` + `CharacterEncodingFilter(forceEncoding=true)` 覆蓋請求/響應。
  2. 應用層:Spring Boot `server.servlet.encoding.*` 自動化配置;非 Spring 項目手動註冊 Filter。
  3. 數據層:JDBC URL 顯式編碼、數據庫 utf8mb4。
  4. 前端層:HTML meta、JS/CSS 文件編碼、Ajax `contentType: 'application/x-www-form-urlencoded; charset=UTF-8'`。
  5. 代理層:Nginx 透傳 Header,避免雙重壓縮/解碼。

遵循上述規範,配合 Arthas 線上驗證,可徹底消滅中文亂碼,構建穩健的國際化 Web 應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved