背景

本文是《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 支持;
  • 通过 AtomikosNarayana 获得 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. 关键理解

  1. Web 容器 ≠ 应用服务器:Tomcat 是 Web 容器,WildFly 才是完整的应用服务器;
  2. 应用服务器 = Web 容器 + EJB 容器 + 全套企业服务:包括 JTA、JMS、JPA、CDI 等;
  3. WildFly 是 Jakarta EE 的主流开源实现:模块化架构、高性能 Undertow、云原生支持;
  4. 部署描述符与注解互补:注解定义默认行为,XML 覆盖或补充;
  5. 类加载器实现模块隔离:Web 应用优先加载自己的类(打破双亲委派),避免版本冲突;
  6. JNDI 是服务发现的统一机制:通过 JNDI 查找数据源、JMS 目的地、EJB 等;
  7. EAR 打包多模块应用:将 Web + EJB 打包为统一部署单元。

3. 与后续学习的衔接

理解应用服务器与容器原理后,你将能更好地理解:

  • 云原生部署:Docker 容器化、Kubernetes 编排;
  • MicroProfile:Jakarta EE 的微服务扩展;
  • Quarkus / Spring Boot:现代 Java 框架如何“内嵌”应用服务器;
  • 性能调优:线程池、连接池、JVM 参数优化;
  • 故障诊断:理解容器日志、类加载问题、内存泄漏排查。