一、起源:Java Web 要解决什么问题
B/S 架构将业务逻辑全部集中到服务器端。服务器端需要完成三件事:解析 HTTP 请求、执行业务逻辑、生成 HTTP 响应。HTTP 协议与 Java 业务代码之间存在三道鸿沟:
| 鸿沟 | HTTP 侧 | Java 业务侧 |
|---|---|---|
| 通信模型 | 无状态、请求-响应、文本协议 | 有状态、对象体系、结构化数据 |
| 生命周期 | 每个请求独立、用完即弃 | 业务对象需要跨请求存活(Session、全局状态) |
| 并发模型 | 每个请求一个连接 | JVM 进程内多线程共享堆内存 |
Java Web 的本质是在这三道鸿沟之上搭建桥梁。早期方案是 CGI(Common Gateway Interface),每个请求 fork 一个进程,在进程内解析 HTTP、执行逻辑、输出响应。进程创建与销毁的开销远大于请求处理本身,并发能力受限。
Java 的解法是让 JVM 常驻运行,请求以线程而非进程的方式进入 JVM。容器负责监听端口、解析 HTTP 协议、构造 Java 对象,再将请求分发给应用代码处理。这一思路的具体规范就是 Servlet。
二、Servlet 规范
Servlet 规范由 JCP(Java Community Process)制定,现移交 Eclipse 基金会管理,命名空间从 javax.servlet 迁移至 jakarta.servlet。规范定义了 Web 容器与 Web 应用之间的契约,是 Java Web 技术体系的核心。
2.1 请求处理契约
容器解析 HTTP 请求后,构造 HttpServletRequest 和 HttpServletResponse 两个对象,调用 Servlet 的 service() 方法。HttpServlet 按请求方法分发至 doGet、doPost、doPut、doDelete 等方法,由开发者实现具体业务逻辑。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// req 封装了 HTTP 请求的全部信息:请求头、参数、Cookie、请求体
// resp 提供了构造 HTTP 响应的能力:状态码、响应头、输出流
String name = req.getParameter("name");
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h1>Hello, " + name + "</h1>");
}
2.2 生命周期契约
| 阶段 | 方法 | 触发时机 | 执行次数 |
|---|---|---|---|
| 初始化 | init(ServletConfig) | 容器加载 Servlet 时 | 1 次 |
| 处理请求 | service() / doGet() 等 | 每次请求到达 | N 次 |
| 销毁 | destroy() | 容器关闭或应用卸载时 | 1 次 |
容器管理 Servlet 实例的创建与销毁。默认情况下,一个 Servlet 类型在容器中只有一个实例,多个请求线程并发调用该实例的 service() 方法。这一设计意味着 Servlet 的实例字段不是线程安全的,业务状态不应存储在实例变量中。
2.3 Filter 链规范
Filter 是 Servlet 规范中的请求拦截机制,位于请求到达 Servlet 之前。多个 Filter 组成有序链,请求依次经过每个 Filter,响应时按逆序返回。
请求 → Filter1 → Filter2 → Filter3 → Servlet
响应 ← Filter1 ← Filter2 ← Filter3 ← Servlet
Filter 可以在请求到达目标前进行预处理(如编码设置、权限校验、日志记录),也可以在响应返回前进行后处理(如响应压缩、头信息修改),甚至可以短路请求(如权限不足时直接返回 403,不调用后续 Filter 和 Servlet)。
2.4 Listener 规范
Listener 用于监听 Web 应用中的三类事件:
| 监听类型 | 接口 | 触发场景 |
|---|---|---|
| ServletContext | ServletContextListener | 应用启动与关闭 |
| HttpSession | HttpSessionListener | Session 创建与销毁 |
| ServletRequest | ServletRequestListener | 请求开始与结束 |
此外还有属性变更监听器,用于监听三类作用域中属性的添加、替换、移除操作。
2.5 Session 规范
HTTP 协议本身无状态,每个请求对服务器而言是独立的。但业务场景需要跨请求维持用户身份(登录状态、购物车内容)。Servlet 规范定义了 HttpSession 机制:
- 客户端首次请求时,容器创建 Session 对象并分配唯一 ID
- 容器通过 Set-Cookie 响应头将 JSESSIONID 返回给客户端
- 后续请求中客户端携带该 Cookie,容器据此找到对应的 Session 对象
- Cookie 被禁用时,容器回退到 URL 重写方式(在 URL 后附加
;jsessionid=xxx)
2.6 异步处理规范(Servlet 3.0+)
传统 Servlet 模型中,容器线程在业务处理完成前一直被占用。对于长耗时操作(如等待外部服务响应),容器线程会被长时间阻塞,高并发场景下线程池耗尽。
Servlet 3.0 引入 AsyncContext,允许容器线程将请求转交给独立线程处理后立即释放,业务线程在完成处理后通过 AsyncContext 写回响应:
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext asyncCtx = req.startAsync();
// 容器线程在此释放,业务在独立线程中执行
executor.submit(() -> {
// 耗时业务逻辑
asyncCtx.getResponse().getWriter().write("done");
asyncCtx.complete(); // 通知容器响应已完成
});
}
}
2.7 非阻塞 IO(Servlet 3.1+)
Servlet 3.1 进一步引入非阻塞 IO 接口 ReadListener 和 WriteListener。与传统阻塞式读写不同,非阻塞 IO 基于事件回调:数据就绪时容器回调 onDataAvailable(),写入就绪时回调 onWritePossible()。这一机制为后续响应式 Web 框架(如 Spring WebFlux)提供了底层支持。
2.8 HTTP/2 支持(Servlet 4.0)
Servlet 4.0 新增对 HTTP/2 协议的支持,包括多路复用(单个 TCP 连接上并发多个请求)和服务器推送(PushBuilder,服务器可主动将资源推送到客户端缓存)。
三、JSP 规范
3.1 JSP 的定位
JSP(JavaServer Pages)的本质是模板引擎,允许在 HTML 中嵌入 Java 代码。容器在首次访问时将 JSP 文件编译为 Servlet 类,后续请求直接调用编译后的 Servlet。因此 JSP 与 Servlet 在运行时是等价的,区别仅在于编写方式。
3.2 JSP 的演进
| 阶段 | 特征 | 问题 |
|---|---|---|
| Model 1 | JSP 同时承担页面渲染和请求处理 | 业务逻辑与页面耦合,维护困难 |
| Model 2(MVC) | Servlet 处理请求、JavaBean 封装数据、JSP 负责渲染 | 分离了关注点,但仍依赖 JSP 作为视图 |
| EL + JSTL | 用表达式语言和标准标签库替代 <% %> 脚本片段 | 减少了页面中的 Java 代码,但仍是服务端渲染 |
| 前后端分离 | JSP 退出,服务端仅返回 JSON | JSP 的模板渲染职责转移至前端框架 |
JSP 中的 EL 表达式(${user.name})和 JSTL 标签库的设计思想,影响了后续的服务端模板引擎(Thymeleaf、Freemarker),但 JSP 本身在前后端分离架构中已基本退出主流。
3.3 JSP 留下的遗产
尽管 JSP 不再广泛使用,其设计中的部分理念仍在延续:
- EL 表达式的
${}语法被 Thymeleaf 等模板引擎继承 - JSTL 的条件、循环标签影响了模板引擎的标签设计
- JSP 编译为 Servlet 的机制,启发了基于字节码生成的框架设计思路
四、WebSocket 规范
HTTP 的请求-响应模型决定了通信只能由客户端发起,服务器无法主动推送数据。对于实时消息、协同编辑、股票行情等场景,客户端需要频繁轮询,效率低下。
WebSocket 协议在单个 TCP 连接上提供全双工通信。Java WebSocket 规范(jakarta.websocket)定义了端点 API:
| 端点类型 | 定义方式 | 特点 |
|---|---|---|
| 注解式 | @ServerEndpoint | 声明式,通过注解标注生命周期方法 |
| 编程式 | 继承 Endpoint 类 | 编程式,需手动重写 onOpen、onClose、onMessage、onError |
WebSocket 连接建立过程复用 HTTP 协议:客户端发送 HTTP Upgrade 请求,服务器返回 101 状态码完成协议切换,此后该连接按 WebSocket 协议通信。
五、设计模式与方法论
5.1 核心设计模式
| 模式 | 在 Java Web 中的体现 | 作用 |
|---|---|---|
| 前端控制器(Front Controller) | Spring MVC 的 DispatcherServlet | 统一请求入口,集中处理路由分发、视图解析、异常处理 |
| 责任链(Chain of Responsibility) | Servlet Filter 链 | 请求依次经过多个处理器,每个处理器可拦截、修改或放行 |
| 模板方法(Template Method) | HttpServlet.service() | 定义处理骨架,按请求方法分发至子类实现的 doGet/doPost |
| 策略(Strategy) | HandlerMapping | 根据配置选择不同的请求映射策略(注解、XML、路径匹配) |
| 适配器(Adapter) | HandlerAdapter | 适配不同类型的 Controller(注解标注、接口实现、方法反射) |
| 视图解析器(View Resolver) | ViewResolver | 将逻辑视图名解析为具体视图实现(JSP、Thymeleaf、JSON) |
5.2 MVC 模式
MVC(Model-View-Controller)将 Web 请求处理分为三个职责:
- Model:业务数据与业务逻辑,不感知 HTTP 上下文
- View:响应的呈现方式(HTML 页面、JSON 文档、文件流)
- Controller:接收请求、调用 Model、选择 View,是 HTTP 与业务之间的翻译层
在前后端分离架构中,View 层的职责从服务端模板渲染转变为 JSON 序列化输出,Controller 直接将 Model 数据序列化为 JSON 返回,不再经过视图解析器。
5.3 约定优于配置
Spring Boot 将这一原则引入 Java Web 开发。框架根据 classpath 中的依赖自动推断配置:引入 spring-boot-starter-web 自动配置内嵌 Tomcat、Spring MVC、Jackson;引入 spring-boot-starter-thymeleaf 自动配置模板引擎。开发者只需在偏离默认值时显式配置。
5.4 Filter / Interceptor / AOP 的分层
三者处于不同的拦截层次,各司其职:
HTTP 请求
│
↓
Filter(Servlet 容器层)
│ 可修改 HttpServletRequest / HttpServletResponse 本身
│ 适用:编码设置、CORS、安全头、日志
↓
DispatcherServlet
│
↓
Interceptor(Spring MVC 层)
│ 可干预 Controller 执行前(preHandle)、后(postHandle)、完成(afterCompletion)
│ 不能修改 HttpServletRequest 对象本身,但可中断执行链
│ 适用:登录校验、权限控制、请求耗时统计
↓
Controller(业务入口)
│
↓
AOP(Spring 容器层)
方法级切面,不感知 HTTP 上下文
适用:日志记录、事务管理、性能监控
选择依据:如果需要操作 HTTP 请求/响应对象本身,用 Filter;如果需要感知 Controller 的执行过程但不依赖 HTTP 细节,用 Interceptor;如果与 Web 层无关(如 Service 层方法的事务、日志),用 AOP。
六、技术演进路线
6.1 演进总览
CGI(进程/请求)
│ 进程创建开销过大
↓
Servlet 2.x(线程/请求,容器管理生命周期)
│ 业务逻辑与页面渲染耦合在 Servlet 中
↓
JSP(模板渲染)→ Model 1(JSP 同时渲染与处理逻辑)
│ Model 1 混乱,引入 Model 2(JSP + Servlet + JavaBean = MVC)
↓
Struts / WebWork(早期 MVC 框架,基于 Action)
│ 配置繁琐、侵入性强
↓
Spring MVC(注解驱动、零侵入、Front Controller 模式)
│ 仍需手动管理容器配置、依赖、部署
↓
Spring Boot(内嵌容器、自动装配、约定优于配置)
│ 同步阻塞模型在高并发下线程开销大
↓
Spring WebFlux(响应式、非阻塞 IO、Reactor)
6.2 各阶段特征
CGI 时代
每个 HTTP 请求由 Web 服务器 fork 一个独立进程处理。进程内解析 HTTP、执行业务逻辑、输出响应后退出。进程创建与销毁的系统调用开销远大于请求处理本身,单机并发能力通常在百级别。
Servlet(1997)
Servlet 规范将请求处理从进程模型改为线程模型。JVM 常驻运行,容器维护线程池,每个请求分配一个线程调用 Servlet 的 service() 方法。线程创建成本比进程低三个数量级(用户态切换 vs 内核态切换),并发能力显著提升。
Servlet 2.x 版本的核心能力:基本请求处理、Filter 链、Listener 机制、Session 管理、web.xml 声明式配置。
JSP(1999)
JSP 解决了 Servlet 中 HTML 拼接的可维护性问题。开发者可以直接在 HTML 中编写页面结构,嵌入 Java 代码处理动态部分。但 Model 1 模式下 JSP 同时承担渲染与逻辑职责,随着页面复杂度增长,维护成本急剧上升。
Model 2 模式将职责拆分:Servlet 处理请求并准备数据,JavaBean 封装业务模型,JSP 只负责渲染。这是 MVC 模式在 Java Web 中的首次落地。
框架时代(2000-2010)
| 框架 | 核心机制 | 局限 |
|---|---|---|
| Struts 1 | 基于 Action 的命令模式,XML 配置路由 | Action 必须继承基类,侵入性强;配置冗长 |
| WebWork | 拦截器栈 + OGNL 表达式 | 与 Struts 1 不兼容 |
| Struts 2 | WebWork 2 的延续,合并 Struts 品牌 | 拦截器设计先进,但后续暴露严重安全漏洞(OGNL 注入) |
| JSF | 组件化渲染,JCP 官方标准 | 组件模型重、学习曲线陡、灵活性不足 |
| Spring MVC | DispatcherServlet + 注解驱动 | 早期仍需大量 XML 配置,Spring Boot 出现后才真正简化 |
Spring MVC
Spring MVC 的核心设计是前端控制器模式。DispatcherServlet 作为唯一入口,内部通过职责分离完成请求处理:
HTTP 请求
↓
DispatcherServlet(前端控制器)
↓
HandlerMapping(请求映射:URL → Controller 方法)
↓
HandlerAdapter(适配不同类型的 Controller)
↓
Controller(业务方法,返回 ModelAndView 或 ResponseEntity)
↓
ViewResolver / MessageConverter(视图解析或 JSON 序列化)
↓
HTTP 响应
Spring MVC 2.5 引入注解驱动配置(@Controller、@RequestMapping),消除了对 XML 路由配置的依赖。Spring MVC 3.0 引入 @ResponseBody 和 HttpMessageConverter,为前后端分离提供了 RESTful JSON API 的原生支持。Spring MVC 作为 Spring Framework 的 Web 子项目,其底层的 Bean 管理、AOP、事务、事件等全部建立在 Spring Core 容器之上,见 Spring Framework 核心体系。
Spring Boot(2014)
Spring Boot 解决了 Spring MVC 在配置管理上的遗留问题:
| 特性 | Spring MVC | Spring Boot |
|---|---|---|
| 容器部署 | 打 WAR 包,部署到外部 Tomcat | 内嵌 Tomcat,打 Fat JAR 直接运行 |
| 配置方式 | XML 或 JavaConfig,手动编写 | 自动装配,按 classpath 依赖推断配置 |
| 依赖管理 | 手动引入各组件,处理版本冲突 | Starter 统一管控版本兼容性 |
| 启动方式 | 容器启动后加载应用 | 应用启动时创建容器 |
响应式 Web(Spring WebFlux,2017)
传统 Servlet 模型是同步阻塞的:每个请求占用一个线程,线程在等待 IO 时被阻塞。高并发场景下,线程池需要维持大量线程,上下文切换开销和内存占用(每线程约 1MB 栈空间)成为瓶颈。
Spring WebFlux 基于 Reactor 响应式编程模型和 Servlet 3.1 非阻塞 IO,以少量线程处理大量并发请求。请求处理流程基于事件回调和数据流推送,线程不阻塞等待 IO,而是在数据就绪时被回调通知。
| 维度 | Spring MVC(同步阻塞) | Spring WebFlux(响应式非阻塞) |
|---|---|---|
| 编程模型 | 命令式,顺序执行 | 声明式,Mono/Flux 数据流 |
| 线程模型 | 一个请求一个线程,IO 时阻塞 | 少量事件循环线程,IO 时不阻塞 |
| 吞吐量 | 受线程池大小限制 | 单机可处理数万并发连接 |
| 延迟特征 | 线程池满时新请求排队 | 无排队,但单请求处理可能略慢 |
| 调试难度 | 调用栈清晰,易于断点调试 | 响应式调用栈断裂,调试困难 |
| 生态兼容 | 全面 | 部分库不支持响应式(如 JDBC) |
WebFlux 不适用于所有场景。CPU 密集型任务无法从非阻塞模型中获益(计算过程本就需要占用线程),IO 密集型且并发量极高的场景才能体现优势。
七、Web 容器
7.1 容器的职责
Web 容器是 Servlet 规范的实现,承担 HTTP 协议层与 Servlet 应用层之间的全部基础设施工作:
- 监听 TCP 端口,接收 HTTP 连接
- 解析 HTTP 请求报文,构造
HttpServletRequest - 根据 URL 映射规则找到目标 Servlet
- 管理 Filter 链的组装与执行
- 管理线程池,为每个请求分配处理线程
- 管理 Servlet、Filter、Listener 的生命周期
- 提供 Session 存储与超时管理
- 序列化 HTTP 响应为报文并发送
7.2 主流容器对比
| 容器 | 定位 | 特点 |
|---|---|---|
| Tomcat | 最广泛使用 | Servlet 规范参考实现,Spring Boot 默认内嵌容器,社区活跃 |
| Jetty | 轻量级 | 启动快、内存占用低,架构基于 Handler,适合嵌入和微服务场景 |
| Undertow | 高性能 | 基于 XNIO,IO 性能优于 Tomcat,JBoss/WildFly 默认容器 |
| Resin | 历史 | 早期支持 PHP/Java 混合部署,现已边缘化 |
| GlassFish | 参考实现 | JCP 官方参考实现,已不再活跃维护 |
Spring Boot 支持通过排除默认 Tomcat 依赖、引入对应 Starter 的方式切换容器:
<!-- 排除 Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入 Undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
7.3 Tomcat 的类加载机制
Tomcat 为每个 Web 应用分配独立的 WebAppClassLoader,打破了双亲委派模型。加载顺序为:先在 Web 应用自己的 classpath(WEB-INF/classes、WEB-INF/lib)中查找,找不到再委派给父加载器。
这一设计的目标是隔离不同 Web 应用的类版本:同一个 Tomcat 实例上部署的应用 A 使用 Spring 4,应用 B 使用 Spring 5,两者的类互不干扰。
关于类加载机制与双亲委派模型的详细说明,见 类加载机制与执行引擎。
八、会话管理
8.1 单机 Session
单机环境下,Session 存储在容器内存中。容器维护一个 Map<String, HttpSession> 结构,以 JSESSIONID 为键。请求到达时从 Cookie 中提取 JSESSIONID,查找对应的 Session 对象。
Session 的超时管理由容器后台线程定期执行,超过 maxInactiveInterval 未被访问的 Session 会被销毁,释放内存。
8.2 分布式 Session
多节点集群部署时,同一用户的请求可能被负载均衡分发到不同节点。如果 Session 仅存于单节点内存,用户在节点 A 登录后,请求被路由到节点 B 时无法识别身份。常见的解决方案:
| 方案 | 机制 | 优势 | 局限 |
|---|---|---|---|
| Session 粘性 | 负载均衡按 IP Hash 将同一用户固定到同一节点 | 无需修改应用代码 | 节点故障时该节点用户的 Session 丢失 |
| Session 复制 | 节点间通过组播同步 Session 数据 | 节点故障不影响可用性 | 网络带宽开销随节点数线性增长 |
| 集中存储 | Session 统一存储到 Redis 等外部存储 | 节点无状态,可自由扩缩 | 增加一次网络 IO,引入外部依赖 |
集中存储是当前主流方案。Spring Session 提供了对 Redis、JDBC 等存储介质的支持,通过 HttpSession 的透明代理实现,应用代码无需感知存储介质的变化。
九、关键实现机制
9.1 请求参数绑定
HTTP 请求参数以字符串形式传输,Java 业务代码需要结构化对象。参数绑定机制完成了这一转换:
| Spring MVC 注解 | 绑定来源 | 转换目标 |
|---|---|---|
@RequestParam | 查询参数或表单字段 | 单个基本类型或 String |
@PathVariable | URL 路径中的变量段 | 单个基本类型或 String |
@RequestBody | 请求体 | 通过 HttpMessageConverter 反序列化为对象(通常为 JSON → POJO) |
@ModelAttribute | 表单字段 | 通过 setter 方法绑定到对象属性 |
@RequestHeader | 请求头 | 单个基本类型或 String |
类型转换由 WebDataBinder 完成,支持自定义 Converter 和 Formatter 扩展。参数校验通过 JSR 380(Bean Validation)注解声明(@NotNull、@Size、@Pattern 等),由 MethodArgumentNotValidExceptionHandler 统一处理校验失败。
9.2 异常处理
Java Web 的异常处理分两个层次:
容器级:web.xml 中配置 error-page,将特定 HTTP 状态码或异常类型映射到指定页面。这是 Servlet 规范定义的机制,与框架无关。
框架级(Spring MVC):@ExceptionHandler 和 @ControllerAdvice 提供方法级的异常拦截能力。@ControllerAdvice 全局生效,@ExceptionHandler 标注的方法在指定异常抛出时被调用,可以统一返回错误码和错误信息。
9.3 文件上传
文件上传基于 multipart/form-data 编码类型。请求体被分割为多个 Part,每个 Part 包含字段名、文件名、内容类型和文件数据。
Servlet 3.0 之前,文件上传依赖第三方库(Apache Commons FileUpload)。Servlet 3.0 引入 @MultipartConfig 注解和 Part 接口,容器原生支持 multipart 解析。Spring MVC 进一步封装为 MultipartFile,提供文件名获取、输入流读取、文件转存等便捷方法。
9.4 跨域处理
浏览器同源策略限制了不同源的 AJAX 请求。CORS(Cross-Origin Resource Sharing)规范通过 HTTP 头协商允许的跨域行为:
- 预检请求(OPTIONS):浏览器先发送 OPTIONS 请求,携带
Origin、Access-Control-Request-Method等头 - 预检响应:服务器返回
Access-Control-Allow-Origin、Access-Control-Allow-Methods等,告知浏览器允许的跨域策略 - 实际请求:预检通过后浏览器发送真实请求
在 Java Web 中,CORS 可在 Filter 层实现(通用拦截),也可在 Spring MVC 层通过 @CrossOrigin 注解或全局 WebMvcConfigurer 配置实现(细粒度控制)。
9.5 字符编码链路
HTTP 报文以字节流传输,Java 字符串需要指定字符集进行编解码。字符编码问题出在请求和响应的多个环节:
浏览器(UTF-8 编码)
↓ URL 编码(如查询参数中文)
Web 服务器(接收字节流)
↓ 解码为字符串(需指定字符集)
Filter(设置 request 编码)
↓ CharacterEncodingFilter
Servlet(读取参数)
↓ 已解码的字符串
业务处理
↓ 输出字符串
Filter(设置 response 编码)
↓ 编码为字节流
Web 服务器(发送字节流)
↓
浏览器(按响应头 charset 解码)
编码不一致会导致中文乱码。CharacterEncodingFilter 应置于 Filter 链最前部,确保后续所有环节使用统一字符集。
十、命名空间迁移:javax → jakarta
2017 年 Oracle 将 Java EE 移交给 Eclipse 基金会。由于 Oracle 保留 javax 商标权,Eclipse 基金会将所有包名从 javax.* 迁移至 jakarta.*。
| 迁移前 | 迁移后 |
|---|---|
javax.servlet | jakarta.servlet |
javax.servlet.http | jakarta.servlet.http |
javax.websocket | jakarta.websocket |
javax.annotation | jakarta.annotation |
Spring Boot 3.x 和 Spring Framework 6.x 全面采用 jakarta 命名空间。基于 Spring Boot 2.x(javax)的项目升级到 3.x 时,所有 import 语句需要批量替换,第三方依赖也需要使用兼容 jakarta 的版本。
十一、现代趋势
11.1 前后端分离
传统 Java Web 中,服务端负责页面渲染(JSP/Thymeleaf),前端是页面的一部分。前后端分离后,服务端仅提供 JSON API,页面渲染由前端框架(React/Vue/Angular)在浏览器中完成。这一变化使 Controller 的职责简化为数据序列化,@RestController + @ResponseBody 成为标准配置。
11.2 API 规范化
| 规范 | 特点 | 适用场景 |
|---|---|---|
| RESTful | 资源导向,HTTP 方法语义化,URL 表达资源层级 | 公开 API、移动端对接、CRUD 类业务 |
| GraphQL | 客户端按需查询字段,单端点灵活组合 | 复杂前端页面需要聚合多源数据 |
| gRPC | 基于 Protocol Buffers 的二进制 RPC 协议 | 服务间内部调用、高性能低延迟场景 |
11.3 容器化部署
Web 容器从外部安装的独立软件变为应用内嵌的组件。Spring Boot 打包为 Fat JAR,包含应用代码、依赖库和内嵌容器,通过 java -jar 直接启动。与 Docker 结合后,每个应用实例运行在独立容器中,环境一致性得到保障。
11.4 同步与响应式的选择
响应式 Web 框架在 IO 密集型高并发场景下具有吞吐量优势,但编程模型复杂、调试困难、生态兼容性受限。多数业务系统的并发量未达到需要响应式模型的阈值,同步阻塞模型(Spring MVC)仍然是主流选择。两者并非替代关系,而是按场景共存。
十二、总结
Java Web 技术体系的核心是 Servlet 规范。规范定义了 Web 容器与应用之间的契约——容器处理 HTTP 协议层,应用处理业务逻辑层,二者通过 HttpServletRequest 和 HttpServletResponse 接口交互。
围绕这一契约,技术演进沿两条线索展开:一是规范自身的迭代(Servlet 3.0 异步、3.1 非阻塞 IO、4.0 HTTP/2、5.0 命名空间迁移),二是框架层的抽象提升(JSP 模板、MVC 模式、Spring Boot 自动装配、WebFlux 响应式模型)。前者的演进方向是让容器更高效地利用资源,后者的演进方向是让开发者更少地处理基础设施细节。
Filter、Interceptor、AOP 构成了从 HTTP 层到方法层的递进式拦截体系。会话管理从单机内存演进到分布式集中存储。前后端分离使服务端职责从页面渲染回归到数据服务。这些变化的共同方向是:业务代码逐步与 HTTP 协议、容器机制、部署方式解耦,开发者越来越聚焦于业务逻辑本身。