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}

四部分:

  1. 请求行 —— 方法 URI 协议版本POST /api/users HTTP/1.1
  2. 请求头 —— 多行 Key: Value(Host、Content-Type、Authorization)
  3. 空行 —— 标记头结束
  4. 请求体 —— 数据(POST/PUT 才有,GET 没有)

HTTP 响应

HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 22

{"id":1,"name":"张三"}

四部分:

  1. 状态行 —— 协议版本 状态码 短语HTTP/1.1 201 Created
  2. 响应头 —— 多行 Key: Value
  3. 空行
  4. 响应体

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-OriginCORS 跨域

Content-Type 常见值

  • application/json —— JSON
  • application/x-www-form-urlencoded —— 表单(a=1&b=2
  • multipart/form-data —— 文件上传
  • text/html —— HTML
  • text/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。

HTTP 是无状态的——服务端不记客户端。但很多场景需要”记住用户”(登录状态、购物车)。SessionCookie 是两种解决方案。

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

对比CookieSession
存储位置客户端(浏览器)服务端(内存/Redis)
大小限制~4KB无(服务端控制)
安全性低(可被 JS 读,可被窃取)高(数据在服务端)
用途偏好设置、跟踪、token登录状态、敏感数据

现代 Web 趋势——Session 移到 Redis(分布式共享),客户端只持有一个 JWT token。

五、Tomcat 服务器

Tomcat 是 Apache 开源的 Servlet 容器——Java Web 应用的运行环境。它干三件事:

  1. 监听端口(默认 8080)接收 HTTP 请求。
  2. 解析请求 转成 HttpServletRequest
  3. 调 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 的极简内核。

Java · 在线运行

观察重点

  • 请求路由到控制器方法——这就是 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 防攻击
TomcatServlet 容器,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 后端。