背景
本文是《JavaEE 后端从小白到大神》修仙系列第十篇,正式进入JavaEE后端世界。若想详细学习请点击首篇博文,我们开始吧。
第十篇:JavaEE 应用服务器与容器原理
- Web 容器 vs 应用服务器;
- Tomcat、WildFly、Payara、GlassFish;
- 部署描述符 web.xml、ejb-jar.xml;
- ClassLoader 结构;
- WAR / EAR 打包与部署;
- 容器生命周期与服务加载。
一、Web 容器 vs 应用服务器
1. 核心概念区分
在 JavaEE/Jakarta EE 体系中,Web 容器和应用服务器是两个经常被混淆但本质不同的概念。
Web 容器(Web Container) :提供 Servlet 和 JSP 的运行环境,处理 HTTP 请求的生命周期管理。典型代表:Apache Tomcat。
应用服务器(Application Server) :实现了完整的 Jakarta EE 规范,除了 Web 容器外,还包含 EJB 容器、JMS、JTA、JPA 等企业级服务。典型代表:WildFly、Payara、GlassFish。
一句话总结:Web 容器是应用服务器的子集——应用服务器 = Web 容器 + EJB 容器 + 全套企业级服务。
2. 容器类型详解
Jakarta EE 应用服务器通常包含多个容器,每个容器负责管理不同类型的组件:
| 容器类型 |
管理对象 |
核心职责 |
| Web 容器 |
Servlet、JSP、JSF |
管理 Web 组件生命周期、分发 HTTP 请求 |
| EJB 容器 |
会话 Bean、MDB |
管理 EJB 生命周期、事务、安全、并发 |
| 应用客户端容器 |
Java SE 客户端应用 |
管理客户端与服务器的通信 |
3. Web 容器 vs 应用服务器:对比总结
| 对比维度 |
Web 容器(Tomcat) |
应用服务器(WildFly/Payara) |
| 支持的规范 |
Servlet、JSP、JSTL、WebSocket |
Jakarta EE 完整规范(全部) |
| EJB 支持 |
❌ 不支持(需扩展) |
✅ 原生支持 |
| JTA 分布式事务 |
❌ 不支持 |
✅ 支持 |
| JMS 消息服务 |
❌ 不支持 |
✅ 支持 |
| CDI 依赖注入 |
❌ 不支持(需扩展) |
✅ 原生支持 |
| 内存占用 |
轻量(~50-100MB) |
较重(~150-500MB) |
| 启动速度 |
快(秒级) |
较慢(数秒至数十秒) |
| 适用场景 |
纯 Web 应用、微服务 |
完整企业级应用 |
4. Tomcat 的特殊定位
Tomcat 常被称为“轻量级应用服务器”,但严格来说它不是完整的 Jakarta EE 应用服务器——它只实现了 Web 容器部分(Servlet + JSP)。它不包含 EJB 容器、JTA 事务管理器、JMS 等企业级服务。
但 Tomcat 可以通过扩展获得部分企业级能力:
- 通过 OpenEJB 获得 EJB 支持;
- 通过 Atomikos 或 Narayana 获得 JTA 分布式事务;
- 通过 ActiveMQ 获得 JMS 消息服务。
选型建议:
- 纯 Web 应用、RESTful API → Tomcat(轻量、快速);
- 需要 EJB、JTA、JMS 的企业级应用 → WildFly / Payara(完整规范)。
二、主流应用服务器
1. WildFly(原 JBoss AS)
WildFly 是 Red Hat 主导的开源 Java 应用服务器,自 2014 年从 JBoss AS 更名而来。它是 Jakarta EE 规范的完整实现,也是 JavaEE 生态中最主流的开源应用服务器之一。
1.1 核心特性
| 特性 |
说明 |
| 模块化架构 |
基于 JBoss Modules 实现按需加载,典型部署仅需 120MB 内存 |
| 完整规范支持 |
全面支持 Jakarta EE 10(Servlet 6.0、JPA 3.1、CDI 4.0 等) |
| 高性能 Web 容器 |
内置 Undertow,支持 HTTP/2,单节点可处理 5 万+并发连接 |
| 分布式事务 |
通过 Narayana 实现 JTA/XA 分布式事务 |
| 热部署 |
支持秒级热部署,无需重启服务器 |
| 云原生支持 |
支持 Docker/Kubernetes 部署,内置健康检查端点 |
1.2 架构设计
WildFly 采用分层模块化架构:
1
2
3
4
5
6
7
8
9
10
11
|
┌─────────────────────────────────────────────────────────────┐
│ Bootstrap(启动引导层) │
├─────────────────────────────────────────────────────────────┤
│ Module Core(模块核心层) │
├─────────────────────────────────────────────────────────────┤
│ Subsystem(子系统层) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Servlet │ │ EJB │ │ JMS │ │ JPA │ │
│ │ 子系统 │ │ 子系统 │ │ 子系统 │ │ 子系统 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
|
关键设计思想:
- 每个功能模块(Servlet 容器、EJB 实现、JMS 服务)以独立子系统形式存在;
- 运行时仅加载必要模块,显著降低内存占用;
- 通过
module.xml 定义模块依赖,实现类加载隔离;
- 采用 MEF(Managed Extension Framework)实现热插拔,支持运行时添加组件。
1.3 两种运行模式
| 模式 |
说明 |
适用场景 |
| Standalone(独立模式) |
单个 JVM 进程运行,管理简单 |
开发测试、中小型项目 |
| Domain(域模式) |
通过域控制器统一管理多台服务器 |
大型分布式系统、集群环境 |
2. Payara
Payara 是基于 GlassFish 代码分支发展而来的应用服务器,由 Payara Foundation 维护。它在保留 GlassFish 管理模型的基础上,增加了生产级特性。
2.1 核心特性
| 特性 |
说明 |
| 完整 Jakarta EE 支持 |
实现完整的 Jakarta EE 规范 |
| 集群与高可用 |
内置集群和会话故障转移 |
| 可扩展事务管理 |
支持大规模分布式事务 |
| 双模式部署 |
支持 Payara Server(完整版)和 Payara Micro(轻量版) |
| Docker 友好 |
提供官方 Docker 镜像 |
2.2 Payara Server vs Payara Micro
| 对比 |
Payara Server |
Payara Micro |
| 用途 |
完整企业级部署 |
命令行或容器友好部署 |
| 体积 |
较大 |
轻量 |
| 管理控制台 |
有 |
无 |
| 适用场景 |
传统企业应用 |
微服务、云原生 |
3. GlassFish
GlassFish 是 Jakarta EE 的参考实现(Reference Implementation) ,由 Eclipse Foundation 维护。
3.1 核心特点
| 特点 |
说明 |
| 标准参考实现 |
完全实现 Jakarta EE 规范,是所有 TCK 的基准 |
| 模块化架构 |
采用 OSGi 标准组件,延迟加载核心服务 |
| 高性能引擎 |
内置 Grizzly 高性能 HTTP 引擎 |
| 双模式部署 |
GlassFish Server(完整版)+ Embedded GlassFish(嵌入式) |
| 管理控制台 |
提供图形化管理界面 |
3.2 GlassFish vs Payara
两者的关系可以这样理解:
Payara 是 GlassFish 的“生产增强版” 。Payara 从 GlassFish 代码分支出来,保留了其管理模型,但增加了集群、高可用、生产级监控等企业特性。
选型建议:
- 追求标准规范、需要参考实现 → GlassFish;
- 需要生产级特性(集群、监控、HA) → Payara;
- 追求高性能、轻量级、云原生 → WildFly。
4. 主流应用服务器对比总结
| 对比维度 |
WildFly |
Payara |
GlassFish |
| 维护方 |
Red Hat |
Payara Foundation |
Eclipse Foundation |
| 定位 |
高性能、模块化 |
生产级企业应用 |
标准参考实现 |
| Jakarta EE 版本 |
10(最新) |
10 |
10 |
| Web 容器 |
Undertow |
Grizzly |
Grizzly |
| 模块化 |
JBoss Modules |
基于 GlassFish |
OSGi |
| 云原生 |
强(Docker/K8s) |
中(Payara Micro) |
中(Embedded) |
| 集群支持 |
✅(Domain 模式) |
✅ |
✅ |
| 管理控制台 |
✅ |
✅ |
✅ |
| 学习曲线 |
中等 |
中等 |
中等 |
三、部署描述符(Deployment Descriptor)
1. 什么是部署描述符
部署描述符是基于 XML 的配置文件,用于描述 JavaEE 应用的组件、配置和部署信息。虽然现代 JavaEE 开发中注解(Annotation) 已大幅减少了 XML 配置,但部署描述符在以下场景中仍然重要:
- 覆盖注解中的默认值;
- 配置无法用注解表达的内容(如安全约束);
- 为不同环境提供不同的配置(开发/测试/生产)。
2. web.xml——Web 应用部署描述符
web.xml 是 Web 应用(WAR 包)的部署描述符,位于 WEB-INF/web.xml。
核心配置项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- 1. Servlet 配置(覆盖注解) -->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.example.LoginServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<!-- 2. 安全约束 -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<!-- 3. 认证方式 -->
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/login-error.html</form-error-page>
</form-login-config>
</login-config>
<!-- 4. 全局参数 -->
<context-param>
<param-name>appName</param-name>
<param-value>MyApp</param-value>
</context-param>
<!-- 5. Session 超时 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!-- 6. 欢迎页面 -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
|
3. ejb-jar.xml——EJB 部署描述符
ejb-jar.xml 是 EJB 模块(JAR 包)的部署描述符,位于 META-INF/ejb-jar.xml。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/ejb-jar_4_0.xsd"
version="4.0">
<!-- 1. 企业 Bean 声明(覆盖注解) -->
<enterprise-beans>
<session>
<ejb-name>OrderService</ejb-name>
<ejb-class>com.example.OrderService</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<!-- EJB 局部参数 -->
<env-entry>
<env-entry-name>maxRetryCount</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>3</env-entry-value>
</env-entry>
</session>
</enterprise-beans>
<!-- 2. 事务属性(覆盖注解) -->
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>OrderService</ejb-name>
<method-name>createOrder</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
<!-- 3. 安全角色 -->
<assembly-descriptor>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</assembly-descriptor>
</ejb-jar>
|
4. application.xml——EAR 部署描述符
application.xml 是企业应用(EAR 包)的部署描述符,位于 META-INF/application.xml。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/application_10_0.xsd"
version="10.0">
<display-name>My Enterprise Application</display-name>
<!-- 应用包含的模块 -->
<module>
<web>
<web-uri>myapp-web.war</web-uri>
<context-root>/myapp</context-root>
</web>
</module>
<module>
<ejb>myapp-ejb.jar</ejb>
</module>
<!-- 安全角色(全局) -->
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</application>
|
5. 部署描述符最佳实践
| 最佳实践 |
说明 |
| 优先使用注解 |
现代 JavaEE 开发中,尽量用 @WebServlet、@Stateless 等注解替代 XML |
| XML 用于覆盖 |
仅在需要覆盖注解默认值时使用部署描述符 |
| 环境隔离 |
为不同环境(开发/测试/生产)维护不同的部署描述符 |
| 安全配置 |
安全约束(角色、URL 保护)建议在 XML 中集中管理 |
| 版本声明 |
始终声明正确的 XML Schema 版本 |
四、ClassLoader 结构
1. Java 类加载器基础
在深入 JavaEE 应用服务器之前,先回顾 Java 的类加载机制。
类加载器(ClassLoader) 负责将类的字节码加载到 JVM 中。Java 类加载器采用双亲委派模型(Parent Delegation Model) :当类加载器被请求加载一个类时,它先把请求委托给父类加载器,只有当父类加载器无法找到该类时,才尝试自己加载。
标准 Java 类加载器层次:
1
2
3
4
5
|
Bootstrap ClassLoader(启动类加载器)
↑ 父
Extension / Platform ClassLoader(扩展/平台类加载器)
↑ 父
System / Application ClassLoader(系统/应用类加载器)
|
2. JavaEE 应用服务器的类加载器层次
JavaEE 应用服务器采用更复杂的树状类加载器结构,以实现模块隔离与资源共享。以 Oracle/Sun 应用服务器为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
┌──────────────────────────────────────────────────────────────────┐
│ Bootstrap Classloader │
│ (加载 JDK 核心类) │
├──────────────────────────────────────────────────────────────────┤
│ System Classloader │
│ (加载应用服务器核心类) │
├──────────────────────────────────────────────────────────────────┤
│ Common Classloader │
│ (加载 domain-dir/lib/ 下的共享库) │
├──────────────────────────────────────────────────────────────────┤
│ Connector Classloader │
│ (加载连接器模块,所有应用共享) │
├────────────────────────────┬─────────────────────────────────────┤
│ LifeCycleModule CL │ EJB Classloader │
│ (生命周期模块)│ (加载 EJB 模块类)│
│ │ ↑ 父 │
│ │ Web Classloader │
│ │ (加载 Web 模块类)│
│ │ ↑ 父 │
│ │ JSP Engine Classloader │
│ │ (加载编译后的 JSP 类) │
│ │ │
└────────────────────────────┴─────────────────────────────────────┘
|
3. 关键类加载器说明
| 类加载器 |
加载范围 |
说明 |
| Bootstrap |
JDK 核心类(java.*) |
JVM 内置,C++ 实现 |
| System |
应用服务器核心类 |
基于 domain.xml 中的 classpath 配置 |
| Common |
domain-dir/lib/ 下的 JAR |
所有应用共享的库 |
| Connector |
连接器模块(JDBC 驱动等) |
所有应用共享的 connector |
| EJB Classloader |
特定 EJB 模块的类 |
每个 EJB 模块一个实例 |
| Web Classloader |
特定 Web 模块的类 |
每个 Web 模块一个实例 |
| JSP Engine CL |
编译后的 JSP 类 |
每个 JSP 文件一个实例 |
4. 双亲委派在 JavaEE 中的特殊处理
标准双亲委派:子类加载器委托给父类加载器。
Servlet 规范的例外:Servlet 规范建议 Web Classloader 优先在本地查找类,再委托给父类加载器。这是因为 Web 应用需要优先使用自己打包的库版本,而非服务器提供的版本(避免版本冲突)。
配置方式(sun-web.xml):
1
2
|
<class-loader delegate="false"/> <!-- 本地优先(打破双亲委派) -->
<class-loader delegate="true"/> <!-- 标准双亲委派(默认) -->
|
5. 类加载器与模块化(WildFly 的 JBoss Modules)
WildFly 采用 JBoss Modules 实现更精细的类加载隔离:
- 每个模块通过
module.xml 定义自己的依赖;
- 模块间显式声明依赖,避免隐式类加载冲突;
- 运行时仅加载必要的模块,减少内存占用。
1
2
3
4
5
6
7
8
9
10
|
<!-- module.xml 示例 -->
<module xmlns="urn:module:1.9" name="com.example.custom">
<resources>
<resource-root path="example.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="org.hibernate"/>
</dependencies>
</module>
|
6. ClassLoader 常见问题与解决方案
| 问题 |
现象 |
解决方案 |
| ClassNotFoundException |
运行时找不到类 |
检查 JAR 是否在正确位置(WEB-INF/lib 或 module) |
| NoClassDefFoundError |
类存在但加载失败 |
检查依赖版本冲突或类加载器隔离问题 |
| ClassCastException |
同一类被不同类加载器加载 |
检查是否有重复 JAR,或使用模块化隔离 |
| 内存泄漏 |
应用卸载后类无法回收 |
避免静态引用、清理 ThreadLocal |
五、WAR / EAR 打包与部署
1. 打包格式概述
JavaEE 应用使用三种标准打包格式:
| 格式 |
文件扩展名 |
内容 |
部署目标 |
| JAR |
.jar |
EJB 模块、工具库 |
EJB 容器 |
| WAR |
.war |
Web 模块(Servlet、JSP、HTML、CSS、JS) |
Web 容器 |
| EAR |
.ear |
企业应用(多个 WAR + JAR 的组合) |
整个应用服务器 |
2. WAR 包结构
myapp.war
├── META-INF/
│ └── MANIFEST.MF # 清单文件
├── WEB-INF/
│ ├── web.xml # Web 部署描述符
│ ├── classes/ # 编译后的 .class 文件
│ │ └── com/example/ # 包路径
│ ├── lib/ # 依赖 JAR 包
│ │ ├── commons-lang.jar
│ │ └── ...
│ └── views/ # JSP 页面(可选)
│ └── user-list.jsp
├── index.html # 静态资源
├── css/ # CSS 文件
└── js/ # JavaScript 文件
关键规则:
WEB-INF/classes/ 下的类优先于 WEB-INF/lib/ 中的 JAR(本地优先原则);
WEB-INF/ 目录不可直接通过浏览器访问(安全保护);
- 静态资源(HTML、CSS、JS)放在
WEB-INF/ 之外,可直接访问。
3. EAR 包结构
myapp.ear
├── META-INF/
│ ├── application.xml # EAR 部署描述符
│ └── MANIFEST.MF
├── myapp-web.war # Web 模块
├── myapp-ejb.jar # EJB 模块
├── myapp-common.jar # 共享库(所有模块共用)
└── lib/ # EAR 级共享库
└── shared-utils.jar
EAR 的优势:
- 将多个模块(Web + EJB)打包为一个统一部署单元;
- 模块间可以共享类库(EAR 级别的
lib/);
- 统一的事务上下文(跨 Web 和 EJB 层);
- 统一的安全角色定义(
application.xml 中的 security-role)。
4. 打包命令
打包 WAR:
1
2
3
4
5
6
7
8
|
# 方式1:使用 jar 命令
jar -cvf myapp.war *
# 方式2:使用 Maven(pom.xml)
mvn package
# 方式3:使用 Gradle
gradle war
|
打包 EAR:
1
2
3
4
5
|
# 使用 jar 命令
jar -cvf myapp.ear *
# 使用 Maven(pom.xml 中 packaging 为 ear)
mvn package
|
5. 部署方式
| 部署方式 |
说明 |
适用场景 |
| 热部署(Hot Deployment) |
将 WAR/EAR 复制到部署目录,服务器自动检测并部署 |
开发、测试 |
| 管理控制台部署 |
通过 Web 管理界面上传并部署 |
生产环境、运维 |
| CLI 命令行部署 |
通过服务器提供的命令行工具部署 |
自动化脚本、CI/CD |
| API 部署 |
通过 REST API 或 JMX 远程部署 |
自动化平台 |
WildFly 部署示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 下载并解压
wget https://download.jboss.org/wildfly/26.1.0.Final/wildfly-26.1.0.Final.zip
unzip wildfly-26.1.0.Final.zip
cd wildfly-26.1.0.Final/bin
# 启动服务器(绑定所有 IP)
./standalone.sh -b 0.0.0.0
# 部署应用(复制到部署目录)
cp myapp.war standalone/deployments/
# 使用 CLI 部署
./jboss-cli.sh --connect command="deploy /path/to/myapp.war"
|
6. WAR vs EAR:选择指南
| 场景 |
推荐格式 |
理由 |
| 纯 Web 应用(只有 Servlet/JSP) |
WAR |
轻量、简单 |
| 包含 EJB 的完整企业应用 |
EAR |
统一部署、事务上下文 |
| 微服务架构 |
WAR(或 JAR) |
轻量、独立部署 |
| 遗留系统维护 |
保持原有格式 |
兼容性 |
六、容器生命周期与服务加载
1. 应用服务器的启动流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
1. 启动脚本(standalone.sh / domain.sh)启动 JVM
↓
2. 加载 Bootstrap Classloader → 加载 JDK 核心类
↓
3. 加载 System Classloader → 加载服务器核心类
↓
4. 解析服务器配置(standalone.xml / domain.xml)
↓
5. 初始化核心服务(日志、命名、安全管理器)
↓
6. 加载并初始化各子系统(Subsystem)
- Web 子系统(Undertow)
- EJB 子系统
- JMS 子系统
- JPA 子系统
- 事务子系统(Narayana)
↓
7. 扫描部署目录,发现待部署的应用
↓
8. 解析部署描述符(web.xml / ejb-jar.xml / application.xml)
↓
9. 创建类加载器(EJB Classloader / Web Classloader)
↓
10. 加载应用类,创建并初始化组件实例
- Servlet 实例化 + init()
- EJB 实例化 + 依赖注入
- 注册 JNDI 名称
↓
11. 启动完成,监听端口,接受请求
|
2. 子系统的加载顺序
在 WildFly 等模块化应用服务器中,各子系统的加载顺序由依赖关系决定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
1. 核心服务层(Core Services)
- 日志(Logging)
- 命名服务(Naming)
- 安全管理器(Security Manager)
2. 基础设施层(Infrastructure)
- 事务管理器(Transaction)
- 数据源(Datasources)
3. 组件容器层(Component Containers)
- EJB 容器
- Web 容器(Undertow)
4. 集成层(Integration)
- JMS 消息服务
- JCA 连接器
5. 应用层(Applications)
- 部署的应用(WAR / EAR)
|
3. 组件的生命周期
Servlet 生命周期(回顾第二章):
1
|
加载类 → 实例化 → init() → service()(多次)→ destroy()
|
EJB 生命周期(回顾第五章):
1
2
3
|
无状态:实例化 → 依赖注入 → @PostConstruct → 业务方法(多次)→ @PreDestroy
有状态:实例化 → 依赖注入 → @PostConstruct → 业务方法 → @Remove → @PreDestroy
单例:实例化 → 依赖注入 → @PostConstruct → 业务方法 → @PreDestroy(应用关闭时)
|
CDI Bean 生命周期(回顾第八章):
1
2
3
4
5
|
@Dependent:随注入者
@RequestScoped:随 HTTP 请求
@SessionScoped:随 HTTP Session
@ApplicationScoped:随应用
@ConversationScoped:手动控制(begin/end)
|
4. 服务加载机制(JNDI)
JNDI(Java Naming and Directory Interface)是 Jakarta EE 的命名和目录服务接口,用于查找和绑定对象。
容器启动时注册的服务:
| 服务类型 |
JNDI 名称示例 |
说明 |
| 数据源 |
java:jboss/datasources/MySQLDS |
数据库连接池 |
| JMS 目的地 |
java:jboss/queue/orderQueue |
消息队列 |
| EJB |
java:global/myapp/OrderService |
企业 Bean |
| 连接工厂 |
java:jboss/ConnectionFactory |
JMS 连接工厂 |
代码中查找 JNDI 资源:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import javax.naming.InitialContext;
import javax.naming.Context;
public class JndiLookupExample {
public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
// 查找数据源
DataSource ds = (DataSource) ctx.lookup("java:jboss/datasources/MySQLDS");
Connection conn = ds.getConnection();
// 查找 EJB(远程)
OrderService service = (OrderService) ctx.lookup(
"java:global/myapp/OrderService");
}
}
|
注入方式(推荐) :
1
2
3
4
5
6
7
8
|
@Stateless
public class MyService {
@Resource(lookup = "java:jboss/datasources/MySQLDS")
private DataSource dataSource;
@Resource(lookup = "java:jboss/queue/orderQueue")
private Queue orderQueue;
}
|
5. 服务加载流程
1
2
3
4
5
6
7
8
9
10
11
12
13
|
1. 容器启动,解析配置文件(standalone.xml / domain.xml)
↓
2. 根据配置创建服务实例(数据源、连接工厂、JMS 目的地等)
↓
3. 将服务实例绑定到 JNDI 名称
↓
4. 应用部署时,解析 @Resource 注解
↓
5. 通过 JNDI 查找对应的服务实例
↓
6. 将服务实例注入到应用的字段或方法参数
↓
7. 应用运行时通过注入的实例使用服务
|
6. 容器生命周期管理最佳实践
| 最佳实践 |
说明 |
| 使用 @PostConstruct / @PreDestroy |
管理组件的初始化和清理逻辑 |
| 使用 @Resource 注入 |
避免硬编码 JNDI 查找字符串 |
| 合理选择作用域 |
根据数据生命周期选择正确的 Scope |
| 监控容器状态 |
使用管理控制台或 JMX 监控服务器健康 |
| 优雅关闭 |
使用 shutdown 命令而非强制 kill,确保 destroy() 执行 |
七、实战案例:在 WildFly 上部署完整企业应用
1. 项目结构
my-enterprise-app/
├── myapp-ear/ # EAR 模块(父模块)
│ └── pom.xml
├── myapp-web/ # Web 模块(WAR)
│ ├── src/main/java/
│ │ └── com/example/web/
│ │ ├── LoginServlet.java
│ │ └── UserResource.java (JAX-RS)
│ ├── src/main/webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ └── views/
│ │ │ └── user-list.jsp
│ │ └── index.html
│ └── pom.xml
├── myapp-ejb/ # EJB 模块(JAR)
│ ├── src/main/java/
│ │ └── com/example/ejb/
│ │ ├── UserService.java (@Stateless)
│ │ ├── OrderService.java (@Stateless)
│ │ └── AuditService.java (@Stateless)
│ └── pom.xml
└── myapp-common/ # 共享库(JAR)
├── src/main/java/
│ └── com/example/common/
│ ├── User.java
│ └── Order.java
└── pom.xml
2. web.xml(Web 模块部署描述符)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<display-name>MyApp Web Module</display-name>
<!-- JAX-RS 应用配置 -->
<servlet>
<servlet-name>JAXRSApplication</servlet-name>
<servlet-class>jakarta.ws.rs.core.Application</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>JAXRSApplication</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!-- 安全约束 -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Resources</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<!-- 表单登录配置 -->
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/login-error.html</form-error-page>
</form-login-config>
</login-config>
<!-- 安全角色 -->
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
<!-- Session 超时 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
|
3. application.xml(EAR 部署描述符)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/application_10_0.xsd"
version="10.0">
<display-name>My Enterprise Application</display-name>
<!-- 模块列表(加载顺序:按声明顺序) -->
<module>
<ejb>myapp-ejb.jar</ejb>
</module>
<module>
<web>
<web-uri>myapp-web.war</web-uri>
<context-root>/myapp</context-root>
</web>
</module>
<!-- 安全角色(全局) -->
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
<!-- 库目录(EAR 级共享库) -->
<library-directory>lib</library-directory>
</application>
|
4. 部署步骤
步骤 1:构建项目
1
2
3
4
5
6
|
# 使用 Maven 构建
cd my-enterprise-app
mvn clean package
# 生成的 EAR 文件位置:
# myapp-ear/target/myapp-ear-1.0.0.ear
|
步骤 2:启动 WildFly
1
2
|
cd wildfly-26.1.0.Final/bin
./standalone.sh -b 0.0.0.0
|
步骤 3:部署 EAR
1
2
3
4
5
6
7
8
|
# 方式1:复制到部署目录(热部署)
cp myapp-ear-1.0.0.ear wildfly-26.1.0.Final/standalone/deployments/
# 方式2:使用 CLI
./jboss-cli.sh --connect command="deploy /path/to/myapp-ear-1.0.0.ear"
# 方式3:管理控制台
# 访问 http://localhost:9990,上传 EAR 文件
|
步骤 4:验证部署
1
2
3
4
5
|
# 查看部署状态
./jboss-cli.sh --connect command="ls deployment"
# 查看日志
tail -f wildfly-26.1.0.Final/standalone/log/server.log
|
步骤 5:访问应用
- Web 应用:
http://localhost:8080/myapp/
- REST API:
http://localhost:8080/myapp/api/users
- 管理控制台:
http://localhost:9990
5. 部署架构图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
┌─────────────────────────────────────────────────────────────────────┐
│ WildFly 应用服务器 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ myapp-ear.ear │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ myapp-ejb.jar │ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │ │
│ │ │ │ EJB 容器 │ │ │ │
│ │ │ │ - UserService (@Stateless) │ │ │ │
│ │ │ │ - OrderService (@Stateless) │ │ │ │
│ │ │ │ - AuditService (@Stateless) │ │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ myapp-web.war │ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │ │
│ │ │ │ Web 容器 │ │ │ │
│ │ │ │ - LoginServlet │ │ │ │
│ │ │ │ - UserResource (JAX-RS) │ │ │ │
│ │ │ │ - JSP 页面 │ │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ lib/(共享库) │ │ │
│ │ │ - myapp-common.jar │ │ │
│ │ │ - shared-utils.jar │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 核心服务(全局) │ │
│ │ - 事务管理器(Narayana) │ │
│ │ - 数据源(MySQLDS) │ │
│ │ - JMS 服务(ActiveMQ Artemis) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
|
八、总结
1. 核心知识脉络
| 层级 |
技术 |
核心概念 |
| 容器类型 |
Web 容器 / 应用服务器 |
Web 容器 = Servlet+JSP;应用服务器 = 完整 Jakarta EE |
| 主流服务器 |
WildFly / Payara / GlassFish |
WildFly(模块化)、Payara(生产增强)、GlassFish(参考实现) |
| 部署描述符 |
web.xml / ejb-jar.xml / application.xml |
XML 配置,覆盖注解 |
| 类加载器 |
ClassLoader 层次结构 |
双亲委派、模块隔离 |
| 打包格式 |
WAR / EAR |
WAR=Web模块,EAR=企业应用 |
| 服务加载 |
JNDI |
命名服务、资源查找与注入 |
2. 关键理解
- Web 容器 ≠ 应用服务器:Tomcat 是 Web 容器,WildFly 才是完整的应用服务器;
- 应用服务器 = Web 容器 + EJB 容器 + 全套企业服务:包括 JTA、JMS、JPA、CDI 等;
- WildFly 是 Jakarta EE 的主流开源实现:模块化架构、高性能 Undertow、云原生支持;
- 部署描述符与注解互补:注解定义默认行为,XML 覆盖或补充;
- 类加载器实现模块隔离:Web 应用优先加载自己的类(打破双亲委派),避免版本冲突;
- JNDI 是服务发现的统一机制:通过 JNDI 查找数据源、JMS 目的地、EJB 等;
- EAR 打包多模块应用:将 Web + EJB 打包为统一部署单元。
3. 与后续学习的衔接
理解应用服务器与容器原理后,你将能更好地理解:
- 云原生部署:Docker 容器化、Kubernetes 编排;
- MicroProfile:Jakarta EE 的微服务扩展;
- Quarkus / Spring Boot:现代 Java 框架如何“内嵌”应用服务器;
- 性能调优:线程池、连接池、JVM 参数优化;
- 故障诊断:理解容器日志、类加载问题、内存泄漏排查。