Servlet 调试

日志、断点、常见错误码与排查顺序。

Intermediate
Key Points先看容器日志找第一条异常,再看状态码定位层,最后才用断点——顺序反了会浪费很多时间。

排查顺序

  1. 看状态码 —— 404 是映射问题,500 是代码抛异常,405 是方法没实现
  2. 看容器日志 —— 找第一条 SEVERE,后面的多半是它的连锁反应
  3. 看浏览器网络面板 —— 请求头、表单数据、响应体是否如预期
  4. 最后打断点 —— 在 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.

常见错误对照

ErrorMeaningFix
404URL 映射不对或应用上下文错核对 @WebServlet 与 getContextPath()
405只实现了 doGet,请求是 POST补 doPost 或改表单方法
500未捕获异常看日志第一条堆栈
ClassNotFoundjavax / jakarta 不匹配,或缺 jar对齐包名,检查 WEB-INF/lib
IllegalState重复取 Writer 或响应已提交只取一次输出流,先设头再写
中文乱码请求或响应未设 UTF-8读参数前设请求编码,写前设响应类型

远程调试

Bash
# 容器启动参数里加 JDWP,然后 IDE 连 5005 端口
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
Never 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.

Friends & Partners