Java Web 基础
欢迎来到第十三阶段——Java Web 与 Spring 生态。前面十二个阶段我们写的都是”控制台程序”——main 方法跑完就退出。但真实世界大部分 Java 程序是 Web 应用——常驻服务器,接收 HTTP 请求,返回 HTTP 响应。这一阶段我们看 Java Web 的底层机制,从 HTTP 协议到 Servlet,从 Tomcat 到 Spring。第一站——HTTP 协议与 Servlet。
一、HTTP 协议详解
HTTP(HyperText Transfer Protocol,超文本传输协议)是 Web 的基石——浏览器和服务端用它通信。它是无状态的请求-响应协议——客户端发请求,服务端返响应,一次结束。
1.1 请求与响应结构
HTTP 请求:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 36
Authorization: Bearer abc123
{"name":"张三","age":20}
四部分:
- 请求行 ——
方法 URI 协议版本(POST /api/users HTTP/1.1) - 请求头 —— 多行
Key: Value(Host、Content-Type、Authorization) - 空行 —— 标记头结束
- 请求体 —— 数据(POST/PUT 才有,GET 没有)
HTTP 响应:
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 22
{"id":1,"name":"张三"}
四部分:
- 状态行 ——
协议版本 状态码 短语(HTTP/1.1 201 Created) - 响应头 —— 多行
Key: Value - 空行
- 响应体
1.2 请求方法
| 方法 | 用途 | 幂等 | 安全 |
|---|---|---|---|
GET | 查询资源 | ✅ | ✅ |
POST | 创建资源 | ❌ | ❌ |
PUT | 全量更新资源 | ✅ | ❌ |
PATCH | 部分更新资源 | ❌ | ❌ |
DELETE | 删除资源 | ✅ | ❌ |
HEAD | 同 GET 但只要头 | ✅ | ✅ |
OPTIONS | 查询支持的方法 | ✅ | ✅ |
幂等(Idempotent) —— 同一请求执行 N 次,效果和执行 1 次相同。GET/PUT/DELETE 是幂等的——重复点击不会出问题。POST 不幂等——重复提交会创建多个资源。
安全(Safe) —— 不修改服务端状态。只有 GET/HEAD/OPTIONS 安全。
RESTful 风格——用 HTTP 方法表达操作:
GET /users # 查询所有用户
GET /users/1 # 查询单个用户
POST /users # 创建用户
PUT /users/1 # 更新用户 (全量)
PATCH /users/1 # 更新用户 (部分)
DELETE /users/1 # 删除用户
1.3 状态码
| 范围 | 含义 | 常见 |
|---|---|---|
| 1xx | 信息 | 101 Switching Protocols(WebSocket 升级) |
| 2xx | 成功 | 200 OK、201 Created、204 No Content |
| 3xx | 重定向 | 301 永久重定向、302 临时重定向、304 Not Modified |
| 4xx | 客户端错 | 400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、429 Too Many |
| 5xx | 服务端错 | 500 Internal Error、502 Bad Gateway、503 Service Unavailable |
记忆要点:
- 401 vs 403 —— 401 是”没登录”(没认证),403 是”登录了但没权限”(没授权)。
- 301 vs 302 —— 301 永久(浏览器缓存,下次直接跳新地址),302 临时(每次都问原地址)。
- 500 vs 502 —— 500 是服务端代码崩了,502 是反向代理收不到上游响应(上游挂了)。
1.4 常用 Header
请求头:
| Header | 含义 |
|---|---|
Host | 目标主机(HTTP/1.1 必需,支持虚拟主机) |
User-Agent | 客户端标识(浏览器类型) |
Accept | 接受的内容类型 |
Content-Type | 请求体类型 |
Authorization | 认证凭证(Bearer token / Basic) |
Cookie | 携带的 Cookie |
Referer | 来源页面 |
响应头:
| Header | 含义 |
|---|---|
Content-Type | 响应体类型 |
Content-Length | 响应体长度 |
Set-Cookie | 设置 Cookie |
Location | 重定向目标(配合 3xx) |
Cache-Control | 缓存策略 |
Access-Control-Allow-Origin | CORS 跨域 |
Content-Type 常见值:
application/json—— JSONapplication/x-www-form-urlencoded—— 表单(a=1&b=2)multipart/form-data—— 文件上传text/html—— HTMLtext/plain—— 纯文本
二、Servlet 体系
Servlet(Server Applet)是 Java Web 的核心——它是运行在 Servlet 容器(如 Tomcat)里的 Java 类,接收 HTTP 请求、产生 HTTP 响应。
2.1 Servlet 接口
public interface Servlet {
void init(ServletConfig config) throws ServletException;
void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
void destroy();
// ... 其他方法
}
通常继承 HttpServlet,按 HTTP 方法分发:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
resp.setContentType("text/html; charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("<h1>Hello, " + name + "</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 处理 POST
}
}
2.2 Servlet 生命周期
init() -- 容器加载时调用一次 (初始化)
↓
service() -- 每个请求调一次 (按方法分发到 doGet/doPost/...)
↓
destroy() -- 容器卸载时调一次 (清理)
init—— 启动时(或第一次访问时)调用一次,做初始化(读配置、连数据库)。service—— 每个请求都调一次,它根据 HTTP 方法分发到doGet/doPost/doPut/doDelete。destroy—— 卸载时调一次,释放资源。
Servlet 是单例的——一个 Servlet 类只一个实例,所有请求共用。所以Servlet 不要有实例变量——多线程并发会冲突。这是初学者最常踩的坑。
2.3 HttpServletRequest / HttpServletResponse
HttpServletRequest 封装请求:
req.getMethod(); // GET/POST
req.getRequestURI(); // /api/users/1
req.getParameter("name"); // 查询参数 / 表单字段
req.getHeader("User-Agent"); // 请求头
req.getCookies(); // Cookie 数组
req.getInputStream(); // 请求体 InputStream
req.getSession(); // 获取 Session
req.setAttribute("k", v); // 请求范围属性 (转发用)
HttpServletResponse 封装响应:
resp.setStatus(200); // 状态码
resp.setHeader("Content-Type", "..."); // 响应头
resp.setContentType("application/json; charset=utf-8");
resp.getWriter().write(json); // 写响应体
resp.getOutputStream(); // 二进制响应体
resp.sendRedirect("/login"); // 重定向
2.4 现代开发:几乎不写 Servlet
直接写 Servlet 很繁琐——一个 URL 一个类,手动解析参数、写响应、处理异常。所以现代 Java Web 都用 Spring MVC——@RestController + @GetMapping 把这些封装得很优雅。但Servlet 是底层——Spring MVC 的 DispatcherServlet 本质就是一个 Servlet。理解 Servlet 才能看懂 Spring 在做什么。
三、Filter 与 Listener
3.1 Filter:请求/响应过滤器
Filter 在请求到达 Servlet 前、响应离开 Servlet 后拦截——常用于鉴权、日志、字符编码、跨域。
@WebFilter("/*")
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
String token = httpReq.getHeader("Authorization");
if (token == null) {
((HttpServletResponse) resp).sendError(401, "未登录");
return; // 不放行
}
chain.doFilter(req, resp); // 放行到下一个 Filter 或 Servlet
}
}
FilterChain 是责任链模式——多个 Filter 串联,按顺序执行,每个 Filter 决定是否放行。
3.2 Listener:事件监听器
Listener 监听容器和应用级事件——启动/销毁、Session 创建/销毁、属性变更。
@WebListener
public class AppInitListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("应用启动, 初始化资源...");
// 加载配置、初始化连接池
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("应用关闭, 清理资源...");
}
}
Spring Boot 启动时做的”初始化数据库连接池、注册 Bean”等,底层就是这类 Listener。
四、Session 与 Cookie
HTTP 是无状态的——服务端不记客户端。但很多场景需要”记住用户”(登录状态、购物车)。Session 和 Cookie 是两种解决方案。
4.1 Cookie:客户端存储
服务器通过响应头 Set-Cookie 给浏览器一坨数据,浏览器后续请求自动带上:
# 响应头
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Max-Age=3600; Secure; SameSite=Lax
# 后续请求头
Cookie: sessionid=abc123; lastLogin=2024-01-15
Cookie 属性:
Domain/Path—— 作用域。Max-Age/Expires—— 过期时间。HttpOnly—— JS 读不到(防 XSS 偷 Cookie)。Secure—— 只走 HTTPS。SameSite—— 跨站限制(防 CSRF)。
4.2 Session:服务端存储
Session 数据存服务端,浏览器只持有一个 JSESSIONID Cookie 当钥匙:
// Servlet 里
HttpSession session = req.getSession();
session.setAttribute("user", currentUser); // 存
User u = (User) session.getAttribute("user"); // 取
session.invalidate(); // 销毁 (登出)
服务端用 JSESSIONID 找到对应 Session 对象。Session 默认 30 分钟没访问就过期。
Cookie vs Session:
| 对比 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务端(内存/Redis) |
| 大小限制 | ~4KB | 无(服务端控制) |
| 安全性 | 低(可被 JS 读,可被窃取) | 高(数据在服务端) |
| 用途 | 偏好设置、跟踪、token | 登录状态、敏感数据 |
现代 Web 趋势——Session 移到 Redis(分布式共享),客户端只持有一个 JWT token。
五、Tomcat 服务器
Tomcat 是 Apache 开源的 Servlet 容器——Java Web 应用的运行环境。它干三件事:
- 监听端口(默认 8080)接收 HTTP 请求。
- 解析请求 转成
HttpServletRequest。 - 调 Servlet 的
service方法处理,把响应写回客户端。
浏览器 --HTTP--> Tomcat --调用--> Servlet --返回响应--> Tomcat --HTTP--> 浏览器
Spring Boot 内嵌 Tomcat——java -jar app.jar 启动就是开个 Tomcat。这就是为什么 Spring Boot 不用部署到外部 Tomcat,是个”独立应用”。
六、实战:模拟 HTTP 请求处理
由于 Servlet 需要 Tomcat 容器,Piston 跑不了。下面用纯 Java SE 模拟一个简易 Web 服务器——接收 HTTP 请求,路由到”控制器”方法,返回响应。这就是 Spring MVC 的极简内核。
观察重点:
- 请求路由到控制器方法——这就是 Spring MVC
DispatcherServlet的本质。- GET 查询、POST 创建、状态码 200/201/404——RESTful 风格的核心。
- JSON 序列化响应——现代 Web API 的标准格式。
- 每个请求一个线程——Tomcat 默认每个请求开一线程(Spring Boot 3.2+ 用虚拟线程优化)。
七、本章小结
| 概念 | 核心要点 |
|---|---|
| HTTP 请求 | 请求行 + 头 + 空行 + 体 |
| HTTP 方法 | GET/POST/PUT/PATCH/DELETE |
| 状态码 | 2xx 成功、3xx 重定向、4xx 客户端错、5xx 服务端错 |
| Servlet | 单例,init/service/destroy 生命周期 |
HttpServletRequest | 封装请求(参数/头/Cookie/Session) |
| Filter | 链式拦截,鉴权/日志 |
| Listener | 监听容器事件 |
| Session | 服务端存储,JSESSIONID 标识 |
| Cookie | 客户端存储,HttpOnly/Secure 防攻击 |
| Tomcat | Servlet 容器,Spring Boot 内嵌 |
记忆口诀:
- HTTP 四段——请求行/头/空行/体。
- 方法表达操作——GET 查、POST 增、PUT 改、DELETE 删。
- 401 vs 403——401 没认证、403 没授权。
- Servlet 单例无状态——别有实例变量。
- Session 服务端 Cookie 客户端——配合用,JSESSIONID 当钥匙。
- 现代用 Spring MVC——不直接写 Servlet。
结语:从 HTTP 到 Spring
这一章我们看了 HTTP 协议和 Servlet——Web 的底层。下一章我们看 Java Web 的”霸主”——Spring 核心,从 IoC/DI 到 AOP,理解为什么 Spring 能统治 Java 后端。