CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Java 进阶/课时/Java Web 框架
RawBlame
课时目录12 节

Java 进阶

  1. 01Java 进阶概览
  2. 02枚举与模式
  3. 03Set/Queue/Stack
  4. 04OOP 进阶
  5. 05方法链与绑定
  6. 06Record 与不可变
  7. 07模块系统
  8. 08并发进阶
  9. 09依赖注入概念
  10. 10Java Web 框架
  11. 11Quarkus 入门
  12. 12综合练习

Java Web 框架

Spring vs Quarkus 概览。

30 分钟

Java Web 框架

这一课你将能做到

  1. 用 JDK 自带的 HttpServer 手写一个最小 HTTP 服务,理解 Servlet 容器到底帮你做了什么
  2. 说出 Spring MVC 一个请求的完整流转路径(DispatcherServlet → HandlerMapping → Controller → 消息转换)
  3. 给 Spring Boot、Quarkus、Micronaut、Vert.x 画出选型判断树,并解释 Nginx 在整个链路中的位置

为什么要学

前 9 课的语言与工程能力,最终都要落到一件事上:对外提供 HTTP 接口。Java 后端的主流答案是 Spring Boot(国内招聘与生态绝对主流),云原生场景 Quarkus 等在增长。但框架再厚,底层都是同一件事——接收 HTTP 请求、路由到代码、返回响应。先亲手摸一遍无框架的最小实现,Spring Boot 的每个注解在你眼里才有对应的实体。

面试角度:「Spring MVC 请求处理流程」「Servlet 生命周期」「过滤器和拦截器的区别」是 Web 方向必考三连;结合本站技术栈答「一个请求从 Nginx 到 MySQL 的完整链路」是高级加分题。

先建立图像

一个请求的完整旅程(本站技术栈视角):

浏览器/App
   │ HTTP
   ▼
Nginx(反向代理:TLS 终止、负载均衡、静态资源、限流)
   │ 转发
   ▼
Tomcat(Servlet 容器:监听端口、解析 HTTP、线程池调度)
   │
   ▼
DispatcherServlet(Spring MVC 前端控制器:统一入口)
   │ HandlerMapping 按 URL+方法 找到 Handler
   ▼
@RestController  ←  @Service  ←  @Repository/JPA
   │ HttpMessageConverter 把对象变 JSON
   ▼
响应原路返回 → Nginx → 客户端

框架分层角色:

角色职责谁来做
Servlet 容器监听端口、解析 HTTP、线程模型Tomcat / Jetty
Web 框架路由、参数绑定、消息转换、拦截Spring MVC / Quarkus REST
应用框架DI、配置、生命周期Spring Boot / Quarkus

跟着做

步骤 1:零依赖手写一个 HTTP 服务

JDK 自带 com.sun.net.httpserver.HttpServer(第 7 课看到的 jdk.httpserver 模块)。新建 MiniWebDemo.java,完整可运行:

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

public class MiniWebDemo {

    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);

        // 「路由」+「处理」+「序列化」,全是你手写的
        server.createContext("/api/orders", MiniWebDemo::handleOrders);

        server.setExecutor(java.util.concurrent.Executors.newFixedThreadPool(4)); // 请求线程池
        server.start();
        System.out.println("试一下: http://localhost:8080/api/orders/1");
    }

    static void handleOrders(HttpExchange exchange) throws IOException {
        String method = exchange.getRequestMethod();
        String path = exchange.getRequestURI().getPath();

        String body;
        int code;
        if ("GET".equals(method) && path.matches("/api/orders/\\d+")) {
            long id = Long.parseLong(path.substring(path.lastIndexOf('/') + 1));
            body = "{\"id\":" + id + ",\"status\":\"PAID\"}";
            code = 200;
        } else {
            body = "{\"error\":\"not found\"}";
            code = 404;
        }
        respond(exchange, code, body);
    }

    static void respond(HttpExchange exchange, int code, String json) throws IOException {
        byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
        exchange.getResponseHeaders().add("Content-Type", "application/json; charset=utf-8");
        exchange.sendResponseHeaders(code, bytes.length);
        try (OutputStream out = exchange.getResponseBody()) {
            out.write(bytes);
        }
    }
}

运行:java MiniWebDemo.java,浏览器或 curl 访问 http://localhost:8080/api/orders/1 与一个不存在的路径看 404。你手写的「路由匹配、状态码、Content-Type、线程池」,正是 Spring MVC 帮你包掉的部分。

步骤 2:同一个接口的 Spring Boot 写法(对照阅读)

在 Spring Boot 项目里,上面 40 行变成 10 行——但每一行都对应步骤 1 里的一个手工环节:

// Spring Boot 项目内代码,依赖 spring-boot-starter-web
package com.example.demo;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @GetMapping("/{id}")
    public OrderView get(@PathVariable long id) {   // ← 对应手写的路径解析
        return new OrderView(id, "PAID");           // ← 对应手写的 JSON 拼接
    }

    record OrderView(long id, String status) {}     // ← 第 6 课的 record 直接做响应体
}

路由、参数绑定、JSON 序列化、异常映射、线程模型全部由框架接管,你只写业务语义。

步骤 3:把 Nginx 放进链路

典型生产配置(阅读理解,不必现在跑):

server {
    listen 443 ssl;
    location /api/ {
        proxy_pass http://127.0.0.1:8080;   # 转发给内网 Tomcat
        proxy_set_header X-Real-IP $remote_addr;
    }
    location / {                            # 静态资源 Nginx 直接扛
        root /var/www/dist;
    }
}

理解分工:Nginx 管 TLS、负载均衡、静态资源与限流;Tomcat + Spring 只处理动态 API。面试聊部署链路时先画这张图。

讲清楚要点

  1. Servlet 容器与 Web 框架分工:容器管「连接与线程」(监听端口、解析字节流、一请求一线程),框架管「请求语义」(路由、绑定、序列化)。Spring Boot 内嵌 Tomcat,java -jar 即起服务。
  2. DispatcherServlet 是前端控制器:所有请求先进它,它调 HandlerMapping 定位 Handler、HandlerAdapter 执行、返回值经 HttpMessageConverter 写回响应。一个入口 + 一套可插拔组件,这是理解 Spring MVC 的总纲。
  3. Servlet 生命周期:init(容器启动或首次请求时初始化一次)→ service(每个请求一个线程调用,单实例多线程并发)→ destroy(容器关闭时释放)。因此 Servlet/Controller 必须无状态。
  4. 过滤器 vs 拦截器:Filter 是 Servlet 规范组件,在 DispatcherServlet 之前生效,管编码、跨域、安全链(Spring Security 就是 Filter 链);Interceptor 是 Spring 机制,能拿到 Handler 信息,在控制器前后做鉴权、日志、耗时统计。先 Filter 后 Interceptor。
  5. 生态选型:Spring Boot(生态与就业首选)、Quarkus(Native/冷启动)、Micronaut(编译期 DI)、Vert.x(事件驱动非阻塞)。概念互通(路由、DI、配置),学透一个再横向迁移成本很低。
  6. starter 的本质:一组「依赖 + 自动配置」的打包。spring-boot-starter-web = 内嵌 Tomcat + Jackson + MVC 自动配置;理解 starter 背后装了什么,才能解释「为什么加了依赖就有功能」。

你可能会懵的地方

  • Tomcat 是服务器还是容器? 两者都是:作为 HTTP 服务器监听端口,作为 Servlet 容器管理 Servlet 生命周期与请求线程。Spring Boot 把它内嵌进 jar,部署形态从「放 war 进 Tomcat」变成「jar 自带 Tomcat」。
  • 一请求一线程,并发 1 万怎么办? 传统方案是 Nginx 限流 + Tomcat 线程池调优 + 快速释放线程(把慢操作丢异步);JDK 21 的虚拟线程让「一请求一线程」模型在阻塞 IO 下也能扛高并发,Spring Boot 3.2+ 一行配置可开启。
  • REST 里「资源」到底指什么? URL 是名词(/api/orders/1),HTTP 方法是动词(GET 查、POST 建、PUT 全量改、DELETE 删),状态码表达结果(200/201/400/401/404/500)。框架无关,任何 Web 框架都遵循。
  • 为什么 Controller 返回 record 就能变 JSON? HttpMessageConverter(Jackson)在响应阶段把对象序列化;Jackson 2.12+ 支持 record 的构造器绑定。第 6 课说过 record 与 JavaBean 不兼容的坑,主流版本已解决。
  • 跨域是后端问题还是前端问题? 浏览器同源策略的产物:服务端通过 CORS 响应头(或 Nginx 层)声明「允许哪些来源访问」。生产常见做法是 Nginx 统一加 CORS 头,应用层不再重复处理。

错法对照

错法问题正解
在 Controller 里写业务与 SQL层职责崩坏,无法复用与测试Controller 只做参数校验与编排
在 Controller/Service 存请求状态到字段单例多线程串数据请求级数据走参数或 ThreadLocal
把鉴权逻辑散落在每个接口重复且易漏Filter/Interceptor 统一处理
返回 200 + body 里塞 error 标志客户端无法按状态码处理正确使用 HTTP 状态码
生产直接暴露 Tomcat 端口绕过 TLS/限流/日志应用只监听内网,Nginx 对外

卡点急救

  1. 端口被占用(Address already in use):8080 被旧进程占着。Windows 下 netstat -ano | findstr :8080 找 PID 再 taskkill /PID xxx /F,或换端口。
  2. 浏览器访问中文乱码:响应头没带 charset。如本课示例,Content-Type: application/json; charset=utf-8 必须显式设置。
  3. Spring Boot 加了依赖却没有对应功能:先确认 starter 是否正确(web vs webflux),再看自动配置日志 --debug 输出里该配置类是否生效;多数问题出在依赖范围或配置项拼写。

面试常问

  • Spring MVC 处理一个请求的完整流程? 可背版:请求到 DispatcherServlet → HandlerMapping 按 URL/方法找到 Handler(Controller 方法)→ HandlerAdapter 执行(期间完成参数绑定/校验)→ 返回值由 HttpMessageConverter 序列化(或走 ViewResolver)→ 响应写回。全程穿插 Interceptor 前后回调与全局异常处理。
  • Servlet 的生命周期?为什么要求无状态? init 仅一次(可配置启动即初始化),service 每请求一次且多线程并发执行同一实例,destroy 仅一次。因为单实例被并发复用,实例字段存请求级数据就会串请求,所以容器与框架组件都要求无状态。
  • 过滤器与拦截器的区别? 可背版:Filter 属于 Servlet 规范,由容器调度,包住 DispatcherServlet 之外的全部请求(含静态资源),典型如编码、CORS、安全链;Interceptor 属于 Spring MVC,能拿到 HandlerMethod 元信息,适合鉴权、日志、耗时监控。执行顺序:Filter → DispatcherServlet → Interceptor → Controller,返回时逆序。
  • 说说一个请求从 Nginx 到数据库的完整链路? Nginx 终止 TLS/负载均衡 → 内网转发到 Tomcat → 请求线程进入 DispatcherServlet → Controller(校验)→ Service(业务、事务)→ Repository/JPA → MySQL(连接池);Redis 在 Service 层做缓存拦截,热点数据不过库。答出「每一跳的职责与线程」即可。

自测三问

  1. MiniWebDemo 里你手写的哪四件事,分别被 Spring MVC 的哪个组件接管?
  2. 为什么 Spring Security 的核心是一串 Filter 而不是 Interceptor?
  3. Nginx 在本站链路里承担哪四类职责?

小结一句话

Web 框架是「路由 + 绑定 + 序列化 + 线程模型」的工厂化封装:先用零依赖代码摸清底层,再看 Spring Boot 就全是熟人。

延伸

  • Spring 官方文档:Web MVC
  • Apache Tomcat 官方文档
  • 下一课:Quarkus 入门
加载本节练习…
← 上一课依赖注入概念下一课 →Quarkus 入门