Servlet 调试
日志、断点、常见错误码与排查顺序。
Key Points先看容器日志找第一条异常,再看状态码定位层,最后才用断点——顺序反了会浪费很多时间。
排查顺序
- 看状态码 —— 404 是映射问题,500 是代码抛异常,405 是方法没实现
- 看容器日志 —— 找第一条
SEVERE,后面的多半是它的连锁反应 - 看浏览器网络面板 —— 请求头、表单数据、响应体是否如预期
- 最后打断点 —— 在
doGet第一行,逐行确认参数与分支
日志
Java
// HttpServlet 自带 log,写到容器的应用日志
log("收到请求 id=" + id);
log("查询失败", exception); // 带堆栈
// ServletContext 级别,应用启动/关闭时用
getServletContext().log("应用级事件");log() adds a timestamp and class name; System.out.println lands in catalina.out with no level and no source, becoming unreadable at volume.
常见错误对照
| Error | Meaning | Fix |
|---|---|---|
| 404 | URL 映射不对或应用上下文错 | 核对 @WebServlet 与 getContextPath() |
| 405 | 只实现了 doGet,请求是 POST | 补 doPost 或改表单方法 |
| 500 | 未捕获异常 | 看日志第一条堆栈 |
| ClassNotFound | javax / jakarta 不匹配,或缺 jar | 对齐包名,检查 WEB-INF/lib |
| IllegalState | 重复取 Writer 或响应已提交 | 只取一次输出流,先设头再写 |
| 中文乱码 | 请求或响应未设 UTF-8 | 读参数前设请求编码,写前设响应类型 |
远程调试
Bash
# 容器启动参数里加 JDWP,然后 IDE 连 5005 端口
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005Never expose a JDWP port publicly: it has no authentication, so anyone who connects can execute arbitrary code. Enable it only locally or behind a jump host, and turn it off afterwards.
打印请求全貌
Java
private static void dump(HttpServletRequest req) {
System.out.println("=== " + req.getMethod() + " " + req.getRequestURI());
System.out.println("query : " + req.getQueryString());
System.out.println("from : " + req.getRemoteAddr());
System.out.println("UA : " + req.getHeader("User-Agent"));
req.getParameterMap().forEach((k, v) ->
System.out.println("param : " + k + " = " + String.join(",", v)));
}Dump the whole getParameterMap() first when debugging parameters: it instantly separates "the client never sent it" from "the server never read it", far faster than scattering printlns.
排查顺序
- 看状态码 —— 404 是映射问题,500 是代码抛异常,405 是方法没实现
- 看容器日志 —— 找第一条
SEVERE,后面的多半是它的连锁反应 - 看浏览器网络面板 —— 请求头、表单数据、响应体是否如预期
- 最后打断点 —— 在
doGet第一行,逐行确认参数与分支
日志
Java
// HttpServlet 自带 log,写到容器的应用日志
log("收到请求 id=" + id);
log("查询失败", exception); // 带堆栈
// ServletContext 级别,应用启动/关闭时用
getServletContext().log("应用级事件");log() 输出带时间戳和类名,比 System.out.println 强——后者进 catalina.out,没有级别、没有来源,日志一多就没法看。
常见错误对照
| 错误 | 含义 | 处理 |
|---|---|---|
| 找不到 | URL 映射不对或应用上下文错 | 核对 @WebServlet 与 getContextPath() |
| 方法不允许 | 只实现了 doGet,请求是 POST | 补 doPost 或改表单方法 |
| 服务端异常 | 未捕获异常 | 看日志第一条堆栈 |
| 类找不到 | javax / jakarta 不匹配,或缺 jar | 对齐包名,检查 WEB-INF/lib |
| 状态非法 | 重复取 Writer 或响应已提交 | 只取一次输出流,先设头再写 |
| 编码不一致 | 请求或响应未设 UTF-8 | 读参数前设请求编码,写前设响应类型 |
远程调试
Bash
# 容器启动参数里加 JDWP,然后 IDE 连 5005 端口
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005远程调试端口绝不能暴露在公网——JDWP 没有认证,任何能连上的人都能执行任意代码。只在本地或跳板机内网临时开启,用完关掉。
打印请求全貌
Java
private static void dump(HttpServletRequest req) {
System.out.println("=== " + req.getMethod() + " " + req.getRequestURI());
System.out.println("query : " + req.getQueryString());
System.out.println("from : " + req.getRemoteAddr());
System.out.println("UA : " + req.getHeader("User-Agent"));
req.getParameterMap().forEach((k, v) ->
System.out.println("param : " + k + " = " + String.join(",", v)));
}调试参数问题时先把 getParameterMap() 整个打出来,一眼就能看出是「前端没发」还是「后端没取到」,比在代码里到处加 println 快得多。