GET 請求中文亂碼是 Java Web 開發經典痛點。根因在於 瀏覽器 URL 編碼行為與服務端解碼行為不一致。本文系統講解三種解決方案的原理、適用場景、優缺點,並給出生產環境推薦配置。
瀏覽器輸入中文
→ 瀏覽器 URL 編碼 (UTF-8/GBK 取決於頁面 charset)
→ HTTP 請求行: GET /api?name=%E4%B8%AD%E6%96%87 HTTP/1.1
→ Tomcat 接收原始位元組
→ Tomcat 解碼 URL (默認 ISO-8859-1) → 字符串 "䏿"
→ Servlet 獲取參數 request.getParameter("name") → 亂碼字符串
%XX%XX 形式。Content-Type: charset=... 指定,默認 UTF-8,通常無亂碼。// Controller 中
String param = request.getParameter("name"); // 獲取 ISO-8859-1 解碼後的錯誤字符串
String correct = new String(param.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
| 優點 | 缺點 | 適用場景 |
|---|---|---|
| 無需修改服務器配置,部署無關 | 侵入業務代碼,易漏寫,需封裝 Filter/Interceptor 統一處理 | 無法控制服務器配置的共享主機、舊項目快速修復 |
<!-- server.xml -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8" />
<!-- 或 useBodyEncodingForURI="true" 配合 request.setCharacterEncoding -->
| 優點 | 缺點 | 適用場景 |
|---|---|---|
| 一勞永逸,代碼零侵入 | 強依賴 Tomcat,遷移 Jetty/Undertow 需重新配置;Docker/K8s 需構建鏡像注入配置 | 自建服務器、傳統 WAR 部署、團隊統一 Tomcat 版本 |
encodeURIComponent (UTF-8) → `%E4%B8%AD` → `%25E4%25B8%25AD`%E4%B8%AD` (UTF-8 編碼形式)// 工具函數
function doubleEncode(str) {
return encodeURIComponent(encodeURIComponent(str));
}
// 使用
const params = {
name: doubleEncode("中文"),
keyword: doubleEncode("搜索內容")
};
axios.get('/api/search', { params });
// 請求 URL: /api/search?name=%25E4%25B8%25AD%25E6%2596%2587&keyword=...
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DoubleEncodeDecodeFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
// 包裝請求,覆寫 getParameter 系列方法
chain.doFilter(new DoubleEncodeRequestWrapper(request), res);
}
}
class DoubleEncodeRequestWrapper extends HttpServletRequestWrapper {
public DoubleEncodeRequestWrapper(HttpServletRequest request) { super(request); }
private String decode(String value) {
if (value == null) return null;
try {
// Tomcat 已解碼一次 (ISO-8859-1),我們再解碼一次 (UTF-8)
return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
} catch (Exception e) {
return value; // 解碼失敗返回原值
}
}
@Override public String getParameter(String name) {
return decode(super.getParameter(name));
}
@Override public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = super.getParameterMap();
map.replaceAll((k, v) -> Arrays.stream(v).map(this::decode).toArray(String[]::new));
return map;
}
@Override public String[] getParameterValues(String name) {
String[] vals = super.getParameterValues(name);
return vals == null ? null : Arrays.stream(vals).map(this::decode).toArray(String[]::new);
}
}
| 優點 | 缺點 | 適用場景 |
|---|---|---|
| 完全符合 HTTP 協議,不依賴特定容器配置,遷移零成本,安全性高 (雙重編碼防 XSS) | 前後端需協同修改,舊接口兼容需雙版本 | 微服務、雲原生、多容器環境、前後端分離項目 (強烈推薦) |
server:
servlet:
encoding:
charset: UTF-8
force: true
enabled: true
# Tomcat 專屬 (Spring Boot 內嵌 Tomcat 自動生效)
tomcat:
uri-encoding: UTF-8
# 兼容雙重編碼場景:若前端已雙重編碼,此處可關閉或保留
# use-body-encoding-for-uri: false
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> encodingFilter() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>(filter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 必須最早執行
return bean;
}
// 若使用雙重編碼方案,DoubleEncodeDecodeFilter 順序需在 CharacterEncodingFilter 之後
// 因為前者依賴 Tomcat 已完成第一次解碼
POST 表單/JSON 亂碼通常由 request.setCharacterEncoding("UTF-8") 未在讀取流前調用導致。Spring Boot CharacterEncodingFilter 已自動處理。**文件上傳需配置 `StandardServletMultipartResolver` 或 Commons FileUpload 設定編碼。**
GET 亂碼本質是編碼協議不匹配。**雙重 URL 編碼方案**通過前後端協同,將編碼/解碼責任納入應用層,擺脫容器配置依賴,是微服務雲原生時代的最優解。建議新項目啟動即確立此規範,舊項目通過 Filter 兼容遷移。徹底告別亂碼,從理解協議開始。