Jakarta EE课程扩展阅读(二)
Jakarta EE课程扩展阅读(二)
1. Servlet 与普通 Java 类的区别
从生命周期、运行环境、实例化方式三个角度来看,Servlet 和一个普通的 Java 类(POJO - Plain Old Java Object)有着本质的区别。
角度 | 普通 Java 类 (POJO) | Servlet |
---|---|---|
生命周期 | 由开发者控制。通过 new 关键字创建,当没有任何引用指向它时,由垃圾回收器(GC)在未来的某个时间点回收。生命周期短暂且不可预测。 | 由容器(Container)管理。其生命周期与服务器或 Web 应用的生命周期绑定,经历加载与实例化 -> 初始化 (init ) -> 服务 (service ) -> 销毁 (destroy ) 四个明确的阶段。 |
运行环境 | 运行在标准的 Java 虚拟机(JVM) 中。它不了解 Web 环境,无法直接处理 HTTP 请求。 | 运行在 Jakarta EE 容器(如 Tomcat, WildFly) 提供的特定环境中。容器为 Servlet 提供了处理 HTTP 请求所需的一切,包括 HttpServletRequest 和 HttpServletResponse 对象。 |
实例化方式 | 由开发者显式创建。例如:MyClass obj = new MyClass(); 。 | 由容器隐式创建。开发者不使用 new 来创建 Servlet 实例。容器在需要时(通常是第一次被请求或服务器启动时)通过反射机制自动创建 Servlet 的实例,并且通常只创建一个单例。 |
核心总结:普通 Java 类是独立的构建块,而 Servlet 是一个被动组件,它的整个生命和行为都由其运行的容器来驱动和管理,专门用于处理 Web 请求。
2. Servlet 生命周期四个阶段
Servlet 的生命周期完全由容器掌控,分为四个核心阶段:
加载与实例化 (Loading and Instantiation)
- 何时发生:
- 延迟加载(默认):当第一个匹配该 Servlet 的 HTTP 请求到达时,容器才会创建它的实例。
- 预加载:如果在
web.xml
或@WebServlet
注解中配置了<load-on-startup>
(值为正整数),容器会在 Web 应用启动时就创建 Servlet 实例。
- 如何发生:容器通过 Java 的反射机制加载 Servlet 类(
Class.forName(...)
)并创建其实例(.newInstance()
)。
- 何时发生:
初始化 (Initialization)
- 何时发生:在 Servlet 实例被创建后,容器会立即调用且仅调用一次
init(ServletConfig config)
方法。 - 作用:这是一个进行一次性初始化操作的绝佳时机,例如:
- 建立数据库连接。
- 加载配置文件或资源。
- 初始化缓存。
- 容器会传入一个
ServletConfig
对象,通过它可以获取部署描述符(web.xml
)中配置的初始化参数。
- 何时发生:在 Servlet 实例被创建后,容器会立即调用且仅调用一次
请求处理 (Request Handling)
- 何时发生:每当有一个客户端请求到达时,容器都会调用
service(ServletRequest, ServletResponse)
方法。 - 如何发生:
- 容器会为每个请求创建一个新的线程(或从线程池中取一个)。
- 该线程负责执行
service()
方法。 - 容器会创建该请求专属的
HttpServletRequest
和HttpServletResponse
对象,并作为参数传入。
- 核心:这是 Servlet 完成主要工作的地方。
HttpServlet
的service()
方法会自动判断请求类型(GET, POST 等),并调用对应的doGet()
,doPost()
等方法。这个阶段会被反复、并发地执行。
- 何时发生:每当有一个客户端请求到达时,容器都会调用
销毁 (Destruction)
- 何时发生:当 Web 应用被卸载或服务器关闭时,容器会调用且仅调用一次
destroy()
方法。 - 作用:用于释放
init()
方法中占用的资源,例如:- 关闭数据库连接。
- 清理缓存。
- 保存状态。
- 在
destroy()
方法执行完毕后,Servlet 实例会被标记为可回收,等待 GC 处理。
- 何时发生:当 Web 应用被卸载或服务器关闭时,容器会调用且仅调用一次
3. Servlet 的历史演进:替代 CGI
在 Servlet 出现之前,动态 Web 内容主要通过 CGI (Common Gateway Interface) 来生成。
CGI 的工作方式:
- 每当一个 Web 请求到达,Web 服务器会启动一个全新的操作系统进程来运行一个外部程序(如一个 Perl 脚本或 C 程序)。
- 这个程序处理请求,生成 HTML,然后退出。
- 服务器将程序的输出返回给浏览器。
CGI 的致命缺点:
- 性能极差:为每个请求都创建一个新进程的开销非常巨大,包括内存分配、进程调度等,导致服务器无法处理高并发。
- 资源消耗高:成百上千的并发请求意味着成百上千个进程,会迅速耗尽服务器资源。
- 非持久化:进程处理完请求就退出,无法方便地维持数据库连接池等共享资源。
Servlet 的革命性优势:
- 基于线程,而非进程:Servlet 实例在内存中是持久化的(单例)。对于每个请求,容器只需创建一个轻量的线程,而不是重量级的进程。这使得 Servlet 能够轻松处理数千个并发连接。
- 资源共享:由于所有请求都由同一个 Servlet 实例的不同线程处理,因此可以轻松共享资源,如数据库连接池。
- 平台无关性:作为 Java 技术,Servlet "一次编写,到处运行",可以部署在任何支持 Jakarta EE 的服务器上。
因此,Servlet 凭借其卓越的性能和资源管理模型,迅速取代了 CGI,成为构建高性能、可伸缩 Web 应用的核心技术。
4. Servlet 与 Jakarta EE:Web 技术体系的基础
在 Jakarta EE 的 Web 技术栈中,Servlet 扮演着基石的角色。它位于最底层,直接与 Web 容器交互,是所有上层 Web 技术的底层支撑。
- JSP 的基础:一个 JSP 文件在第一次被访问时,会被容器编译成一个 Servlet 类。后续所有对该 JSP 的访问,实际上都是在执行这个编译后的 Servlet。
- JSF 的基础:JSF 框架的核心是一个名为
FacesServlet
的前端控制器 Servlet。所有对.jsf
或.xhtml
页面的请求都会被这个 Servlet 拦截,然后由它分发给 JSF 的生命周期处理器。 - JAX-RS (REST) 的基础:大多数 JAX-RS 的实现(如 Jersey, RESTEasy)也是通过一个核心的 Servlet 来接收所有 REST API 请求,然后根据 URL 和注解将请求路由到正确的资源方法。
- Spring MVC 的基础:正如你将在下一个问题中看到的,Spring MVC 的核心
DispatcherServlet
本质上就是一个标准的 Servlet。
结论:无论你使用哪种上层 Java Web 框架,最终处理 HTTP 请求的入口点几乎总是 Servlet。理解 Servlet 是理解整个 Java Web 工作原理的关键。
5. Servlet 规范发展:从 2.x 到 6.0 的主要变化
Servlet 规范的演进反映了 Java Web 开发模式的变迁。
Servlet 2.x (XML 配置时代):
- 配置方式:所有 Servlet、Filter、Listener 的配置都必须在部署描述符文件
web.xml
中完成。 - 缺点:当项目变大时,
web.xml
会变得异常臃肿和难以维护。
- 配置方式:所有 Servlet、Filter、Listener 的配置都必须在部署描述符文件
Servlet 3.0 (注解与异步时代 - 革命性变革):
- 配置方式:引入了 注解(如
@WebServlet
,@WebFilter
,@WebListener
),使得配置可以直接写在 Java 类中,web.xml
变为可选。 - 主要功能:
- 异步处理:允许将耗时的请求(如 I/O 操作)移交给一个后台线程处理,从而释放容器的主线程去接收更多请求,极大地提升了应用的吞吐量。
- 动态配置:允许通过代码动态地添加 Servlet 和 Filter。
- 配置方式:引入了 注解(如
Servlet 3.1 & 4.0 (现代化与 HTTP/2):
- 主要功能 (3.1):引入了非阻塞 I/O,允许在读写数据时也不阻塞线程,是异步处理的进一步增强。
- 主要功能 (4.0):最核心的更新是支持 HTTP/2,包括服务器推送(Server Push)等新特性。
Servlet 5.0 (Jakarta EE 时代):
- 核心变化:命名空间从
javax.servlet.*
变更为jakarta.servlet.*
。这是由于 Java EE 迁移到 Eclipse 基金会后,Oracle 不允许其继续使用javax
商标。这是一个重大的破坏性变更,所有代码都需要修改import
语句。 - 功能:在功能上与 4.0 基本保持一致,主要目标是完成迁移。
- 核心变化:命名空间从
Servlet 6.0 (精简与未来):
- 核心变化:移除了许多已废弃的功能,例如 EJB 和安全相关的旧 API,使得整个规范更加精简和现代化。
- 目标:更好地适应云原生和微服务环境,为未来的发展扫清障碍。
6. Servlet 与现代框架:Spring MVC 的基石
现代 Web 框架(如 Spring MVC)并没有抛弃 Servlet,而是巧妙地建立在 Servlet 之上,提供了一个更高层次的抽象,让开发者可以更专注于业务逻辑。
以 Spring MVC 为例,其工作流程完美地诠释了这种关系:
前端控制器模式 (Front Controller Pattern):Spring MVC 的核心是一个名为
DispatcherServlet
的类。它本身就是一个实现了标准jakarta.servlet.http.HttpServlet
接口的 Servlet。配置:在应用启动时,Web 容器(如 Tomcat)会加载并初始化这个
DispatcherServlet
,并让它拦截所有(或指定的)Web 请求。请求处理流程:
- 一个 HTTP 请求(如
GET /users/123
)到达 Tomcat。 - Tomcat 发现这个请求的 URL 模式被
DispatcherServlet
注册了,于是将HttpServletRequest
和HttpServletResponse
对象传递给DispatcherServlet
的service()
方法。 DispatcherServlet
接管请求后,不再需要开发者直接处理底层的 Request/Response 对象。它会查询其内部的处理器映射(HandlerMapping),找到能够处理/users/123
这个路径的、由开发者编写的@Controller
类中的某个方法。DispatcherServlet
调用该方法,并将请求参数自动绑定到方法的参数上。- 开发者的方法返回一个模型和视图名。
DispatcherServlet
根据视图名找到对应的视图(如一个 JSP 文件),渲染模型数据,并将最终的 HTML 写入HttpServletResponse
。
- 一个 HTTP 请求(如
总结:Spring MVC 等框架就像一个高级的“任务分配中心”。它自己作为一个标准的 Servlet 在底层与容器打交道,然后将请求优雅地分派给开发者编写的业务处理单元(Controller),从而将开发者从繁琐的底层 Servlet API 中解放出来。