楼层: 首页/ 软件技术/ JavaEE / Jakarta EE/ Servlet:Web 后端的最小单位
02

Servlet:Web 后端的最小单位

Servlet Lifecycle · Request / Response · Forward & Redirect

浏览器输入网址回车,背后发生了什么?你的请求一路到达Servlet 容器(比如 Tomcat),容器把 HTTP 请求包装成 HttpServletRequest 对象,调用你写的 Servlet 方法,再把你 HttpServletResponse 里写的内容翻译成 HTTP 响应发回浏览器。Servlet 就是"接请求、干活、回响应"的那一个函数。

论Servlet 一生三阶段

init():第一次有人访问这个 Servlet 时调用一次,做初始化(整个生命周期只跑一次)。

service() / doGet() / doPost():每次请求来都跑一遍。注意——多线程:并发请求同时跑同一个 Servlet 实例的 doGet,成员变量要小心共享。

destroy():容器关闭时调用一次,做清理。

人话:Servlet 是单身汉——整个类只有一个实例,所有用户共用。你在类里放个可变成员变量,等于把公共钱包扔在大堂,谁都能改。

论一个请求的完整旅程

把这一章串成一条线:① 浏览器发出 HTTP 请求(方法 + URL + 请求头 + 可能的表单体);② Tomcat 监听端口收到,解析成 HttpServletRequest;③ 按 @WebServlet 的路径找到你的 Servlet,调用 service(),内部按方法分发到 doGet/doPost;④ 你往 HttpServletResponse 里写东西;⑤ Tomcat 把它包成 HTTP 响应发回浏览器。

记住这条线,以后学 Spring 的 @RequestMapping,你会发现它只是把第 ③ 步自动化了。所谓框架,就是把你写过十遍的样板,替你写了第一百遍。

一个完整的 Servlet:接收参数、写回 HTML(注解配置,不用 web.xml)

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;

// @WebServlet 就是路由:访问 /hello 就来这个类(Servlet 3.0+ 起不用写 web.xml)
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {

        // 1. 取浏览器传过来的参数:?name=小明
        String name = req.getParameter("name");
        if (name == null || name.isBlank()) name = "陌生人";

        // 2. 设置响应类型和编码,告诉浏览器"我回你的是 HTML,UTF-8"
        resp.setContentType("text/html;charset=UTF-8");

        // 3. 往响应体里写 HTML
        resp.getWriter().write("<h1>你好," + name + "</h1>");
    }
}

请求转发 vs 重定向:新手第一大懵圈点

请求转发 forward重定向 sendRedirect
网址栏变不变不变,还停在原 URL。变,浏览器地址栏跳到新 URL。
几次请求一次(服务器内部转交)。两次(服务器让浏览器再发一次请求)。
数据能不能带能,request 域里的属性还在。不能,新请求拿不到旧 request。
什么时候用查完数据交给 JSP 展示。表单提交后跳成功页(防刷新重复提交)。

补一课:HTTP 常见状态码,看到就知道咋回事

浏览器和服务器之间对话,靠状态码报数。写 Servlet 时你会频繁见到它们,混个眼熟,排错速度翻倍。

状态码含义什么时候见到 / 怎么办
200OK正常返回,皆大欢喜。
302临时重定向sendRedirect 发出的就是这个,浏览器自动跳新地址。
404Not Found请求的路径没对应任何 Servlet——@WebServlet 路径写错,或应用没部署对。
405Method Not AllowedGET 打到了只重写 doPost 的 Servlet。doGet/doPost 对不上。
500服务器内部错误你的代码抛异常了——去 Tomcat 控制台找 Caused by。

两者对照写法

// 请求转发:服务器内部把请求递给 show.jsp,网址不变
req.setAttribute("list", list);          // 往 request 里塞数据
req.getRequestDispatcher("/show.jsp").forward(req, resp);

// 重定向:让浏览器自己再请求一次 /success,网址会变
resp.sendRedirect("/success");

@WebServlet 常用属性与多路径

一个 Servlet 映射多个 URL、设置初始化参数,都靠这个注解。

注解的完整写法(了解即可,日常只用 urlPatterns)

@WebServlet(
    name = "userServlet",                 // 名字,不写默认类名
    urlPatterns = {"/user", "/u"},       // 两个路径都进这个 Servlet
    loadOnStartup = 1,                  // 启动时就初始化(数字小的先加载)
    initParams = {
        @WebInitParam(name = "encoding", value = "UTF-8")
    }
)
public class UserServlet extends HttpServlet {}

404 / 500 怎么查:别只盯着浏览器

请求不通时,按这个顺序排查,比瞎重启快十倍。

现象查哪里
404① 路径拼写和 @WebServlet 对不对;② 应用 context path(访问 URL 是 /board/hello 还是 /hello);③ 项目到底部署上去没有。
405doGet/doPost 和表单 method 对不上。
500打开 Tomcat 控制台日志,找 Caused by 那一行——异常栈最底下才是根因。
页面空白按 F12 看网络请求状态码,别以为是 CSS 没加载。

Servlet 里怎么调试:打印是最好的朋友

后端不像前端能打断点随手看,初学阶段最朴素的办法:关键节点 System.out.println(...) 打到 Tomcat 控制台。进没进这个方法?参数拿到没有?走到哪一步挂了?一行日志全知道。熟练后再用 IDE 的远程断点。

doGet / doPost 别混

表单 method="post" 你却只重写了 doGet,Tomcat 会回你一个 405 Method Not Allowed。反过来,GET 请求丢到 doPost 也是 405。通用偷懒法:让 doPost 里直接调 doGet(req, resp),两个方法共用一套逻辑。另外:GET 的参数在网址上,别用它传密码;POST 才适合提交敏感数据。

章末面试 · Servlet(5 题)

1.(概念题)Servlet 生命周期三个方法?哪个只跑一次?

查看答案

答案:init()(第一次访问初始化,只跑一次)、service()/doGet/doPost(每次请求都跑)、destroy()(容器关闭时跑一次)。

2.(概念题)Servlet 是单例吗?成员变量有什么坑?

查看答案

答案:是单例,整个类只有一个实例,多线程并发访问 doGet。成员变量会被所有用户共享,等于公共钱包扔大堂。请求数据一律用局部变量或 request.setAttribute。

3.(选择题)请求转发 forward 和重定向 sendRedirect,哪个地址栏会变?

查看答案

答案:重定向会变(两次请求),转发不变(一次请求服务器内部转交)。转发能共享 request 数据,重定向不能。

4.(排错题)表单 method="post" 提交后返回 405,为什么?

查看答案

答案:你只重写了 doGet,没重写 doPost。Tomcat 收到 POST 找不到对应方法。通用偷懒法:让 doPost 直接调 doGet。

5.(排错题)访问 /hello 报 404,可能哪几个原因?

查看答案

答案:① @WebServlet 路径写错;② 应用 context path 不对(实际 URL 是 /board/hello 不是 /hello);③ 项目根本没部署上去。按这三个顺序查。