Servlet:Web 后端的最小单位
浏览器输入网址回车,背后发生了什么?你的请求一路到达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 时你会频繁见到它们,混个眼熟,排错速度翻倍。
| 状态码 | 含义 | 什么时候见到 / 怎么办 |
|---|---|---|
| 200 | OK | 正常返回,皆大欢喜。 |
| 302 | 临时重定向 | sendRedirect 发出的就是这个,浏览器自动跳新地址。 |
| 404 | Not Found | 请求的路径没对应任何 Servlet——@WebServlet 路径写错,或应用没部署对。 |
| 405 | Method Not Allowed | GET 打到了只重写 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);③ 项目到底部署上去没有。 |
| 405 | doGet/doPost 和表单 method 对不上。 |
| 500 | 打开 Tomcat 控制台日志,找 Caused by 那一行——异常栈最底下才是根因。 |
| 页面空白 | 按 F12 看网络请求状态码,别以为是 CSS 没加载。 |
Servlet 里怎么调试:打印是最好的朋友
后端不像前端能打断点随手看,初学阶段最朴素的办法:关键节点 System.out.println(...) 打到 Tomcat 控制台。进没进这个方法?参数拿到没有?走到哪一步挂了?一行日志全知道。熟练后再用 IDE 的远程断点。
表单 method="post" 你却只重写了 doGet,Tomcat 会回你一个 405 Method Not Allowed。反过来,GET 请求丢到 doPost 也是 405。通用偷懒法:让 doPost 里直接调 doGet(req, resp),两个方法共用一套逻辑。另外:GET 的参数在网址上,别用它传密码;POST 才适合提交敏感数据。
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);③ 项目根本没部署上去。按这三个顺序查。