什么是 EJB?
EJB(Enterprise JavaBeans)是 Java EE(现更名为 Jakarta EE)平台的核心组件模型之一。它是一个服务端组件架构,用于开发分布式的、事务安全的、可扩展的企业级 Java 应用程序。
EJB 的设计初衷是将业务逻辑从客户端剥离,集中到服务器端执行,从而简化客户端的开发,并让服务器统一管理事务、安全、并发等企业级特性。

在 Java 生态中,EJB 曾经是企业开发的代名词。从 2000 年代初到 2010 年前后,几乎所有大型 Java 项目都在使用 EJB。虽然后来被 Spring 框架大幅取代,但理解 EJB 仍然非常重要——它是理解 Java 企业级开发演进的必经之路。
EJB 的实现原理:RMI 远程方法调用
EJB 是运行在独立服务器上的组件,客户端是通过网络对 EJB 对象进行调用的。在 Java 中,能够实现远程对象调用的技术是 RMI(Remote Method Invocation),而 EJB 的技术基础正是 RMI。
RMI 的核心原理
RMI 通过 Java 对象的序列化机制实现分布式计算。它的工作流程如下:
- 客户端调用远程对象的本地代理(Stub)
- Stub 将方法名、参数等进行序列化
- 序列化后的数据通过网络传输到服务器端
- 服务器端的 Skeleton 反序列化请求数据
- 调用实际的业务对象执行方法
- 将执行结果序列化后返回给客户端
1 2 3 4 5 6 7 8 9 10 11
| ┌─────────────┐ 网络传输 ┌─────────────┐ │ 客户端 │ ── 序列化请求 ──> │ 服务端 │ │ │ │ │ │ Stub │ <── 反序列化结果 ── │ Skeleton │ │ (本地代理) │ │ (服务骨架) │ └─────────────┘ └─────────────┘ │ ┌─────┴─────┐ │ 实际对象 │ │ (业务逻辑) │ └───────────┘
|
EJB 如何利用 RMI
EJB 将原来需要写在客户端的代码转移到了服务器端,并依靠 RMI 进行通信。客户端只需要通过 JNDI(Java Naming and Directory Interface)查找 EJB 的远程引用,就可以像调用本地方法一样调用远程业务逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import javax.naming.InitialContext; import javax.naming.Context;
Context ctx = new InitialContext();
Object obj = ctx.lookup("java:global/MyApp/CalculatorBean");
CalculatorRemote calculator = (CalculatorRemote) javax.rmi.PortableRemoteObject.narrow(obj, CalculatorRemote.class);
int result = calculator.add(10, 20); System.out.println("结果: " + result);
|
RMI 的底层机制
RMI 的远程对象通信涉及两个关键概念:
- 序列化(Serialization):将 Java 对象转换为字节流,以便通过网络传输或持久化存储
- 反序列化(Deserialization):将字节流还原为 Java 对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.io.*;
public class RemoteData implements Serializable { private static final long serialVersionUID = 1L; private String name; private int value;
public RemoteData(String name, int value) { this.name = name; this.value = value; } }
|
EJB 的三种 Bean 类型
EJB 规范定义了三种核心 Bean 类型,分别对应不同的业务场景。
1. Session Bean(会话 Bean)
Session Bean 用于封装业务逻辑,是最常用的 EJB 类型。它分为两种:
1.1 Stateless Session Bean(无状态会话 Bean)
容器维护一个 Bean 实例池,客户端每次调用可能分配到不同的实例。适合无状态的业务逻辑(如计算服务、查询服务)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import javax.ejb.Stateless;
@Stateless(name = "CalculatorBean") public class CalculatorBean implements CalculatorRemote {
@Override public int add(int a, int b) { return a + b; }
@Override public int multiply(int a, int b) { return a * b; } }
import javax.ejb.Remote;
@Remote public interface CalculatorRemote { int add(int a, int b); int multiply(int a, int b); }
|
1.2 Stateful Session Bean(有状态会话 Bean)
容器为每个客户端维护一个专属的 Bean 实例,可以在多次调用之间保持状态。适合购物车、向导式流程等场景。
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
| import javax.ejb.Stateful; import java.util.ArrayList; import java.util.List;
@Stateful public class ShoppingCartBean implements ShoppingCartRemote {
private List<String> items = new ArrayList<>();
@Override public void addItem(String item) { items.add(item); }
@Override public List<String> getItems() { return new ArrayList<>(items); }
@Override public double getTotal() { return items.size() * 10.0; }
@Remove @Override public void checkout() { System.out.println("结账,共 " + items.size() + " 件商品"); } }
|
2. Entity Bean(实体 Bean)
Entity Bean 用于持久化数据到数据库,相当于数据库表在 Java 中的对象映射。在 EJB 2.x 时代使用 CMP(容器管理持久化),后来被 JPA(Java Persistence API)取代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import javax.ejb.EntityBean;
public class UserBean implements EntityBean { private EntityContext ctx; private String id; private String name; private String email;
public abstract String getId(); public abstract void setId(String id); public abstract String getName(); public abstract void setName(String name); public abstract String getEmail(); public abstract void setEmail(String email);
public void ejbCreate(String id, String name, String email) { this.id = id; this.name = name; this.email = email; } }
|
3. Message-Driven Bean(消息驱动 Bean)
MDP(Message-Driven Bean)用于异步处理消息。它监听 JMS(Java Message Service)消息队列,收到消息后自动触发处理逻辑。
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
| import javax.ejb.ActivationConfigProperty; import javax.ejb.MessageDriven; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage;
@MessageDriven( activationConfig = { @ActivationConfigProperty( propertyName = "destinationType", propertyValue = "javax.jms.Queue" ), @ActivationConfigProperty( propertyName = "destination", propertyValue = "queue/OrderQueue" ) } ) public class OrderProcessorBean implements MessageListener {
@Override public void onMessage(Message message) { try { if (message instanceof TextMessage) { String orderData = ((TextMessage) message).getText(); System.out.println("收到订单消息: " + orderData);
processOrder(orderData); } } catch (Exception e) { System.err.println("处理订单失败: " + e.getMessage()); } }
private void processOrder(String orderData) { System.out.println("正在处理订单..."); } }
|

服务器集群与分布式架构
EJB 的另一个重要特性是服务器集群。通过 RMI 通信,可以将不同功能模块部署到不同的服务器上,形成一个完整的分布式系统。
集群架构示意
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
| ┌──────────┐ │ 负载均衡 │ │ (F5/Nginx)│ └────┬─────┘ │ ┌───────────────┼───────────────┐ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ Web层 │ │ Web层 │ │ Web层 │ │ (Tomcat)│ │ (Tomcat)│ │ (Tomcat)│ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ └───────────────┼───────────────┘ │ ┌──────────┴──────────┐ │ EJB 容器层 │ │ ┌───────────────┐ │ │ │ Session Bean │ │ │ │ Entity Bean │ │ │ │ Message Bean │ │ │ └───────────────┘ │ └──────────┬──────────┘ │ ┌─────┴─────┐ │ 数据库层 │ │ (Oracle) │ └───────────┘
|
集群的关键概念
| 概念 |
说明 |
| 负载均衡 |
将请求均匀分配到多个服务器 |
| 会话复制 |
在多个服务器之间同步用户会话状态 |
| 故障转移 |
当某台服务器宕机时,自动将请求转移到其他服务器 |
| 水平扩展 |
通过增加服务器数量来提升系统处理能力 |
EJB 与 Spring 框架的对比
Spring 框架的崛起,很大程度上是因为它提供了一种比 EJB 更轻量的企业级开发方式。
对比分析
| 对比维度 |
EJB (2.x/3.x) |
Spring Framework |
| 编程模型 |
必须实现接口,继承特定基类 |
普通 Java 对象(POJO),无需继承 |
| 配置方式 |
早期需要大量 XML,3.x 用注解 |
灵活的 XML + 注解 + Java Config |
| 容器依赖 |
必须运行在 EJB 容器(JBoss/WebLogic) |
可以在任何 Servlet 容器中运行 |
| 事务管理 |
容器管理事务(CMT),侵入性强 |
声明式事务(@Transactional),非侵入 |
| 学习曲线 |
陡峭,概念繁多 |
平缓,渐进式学习 |
| 测试难度 |
困难,需要启动 EJB 容器 |
简单,支持独立单元测试 |
| 性能 |
早期较差,3.x 改善明显 |
轻量,性能好 |
Spring 如何实现类似 EJB 的功能
Spring 通过依赖注入(DI)和面向切面编程(AOP),实现了 EJB 的大部分企业级特性:
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
| import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service @Transactional public class OrderService {
@Autowired private OrderRepository orderRepository;
public Order createOrder(Order order) { return orderRepository.save(order); } }
import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component;
@Component public class OrderMessageListener {
@JmsListener(destination = "OrderQueue") public void processOrder(String orderData) { System.out.println("收到订单: " + orderData); } }
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> { List<Order> findByUserId(Long userId); }
|
现代微服务视角下的 EJB
站在今天(2024+)的视角来看 EJB,它有着深刻的历史意义,但也暴露出了明显的时代局限性。
EJB 的历史贡献
- 最早的企业级组件模型:定义了事务、安全、并发等企业级特性的标准处理方式
- 推动了 Java 企业化:让 Java 从桌面语言变成了企业开发的主力语言
- 催生了 Spring:EJB 的复杂性直接催生了 Spring 框架的诞生
现代替代方案
在今天,如果要构建分布式企业系统,通常会采用以下技术栈:
| 需求 |
EJB 时代 |
现代方案 |
| 远程调用 |
RMI / EJB Remote |
gRPC / REST API |
| 事务管理 |
CMT |
Spring @Transactional / Seata |
| 消息处理 |
JMS + MDB |
Kafka / RabbitMQ |
| 依赖注入 |
EJB @Inject |
Spring @Autowired / CDI |
| 持久化 |
Entity Bean (CMP) |
JPA / MyBatis |
| 服务编排 |
EJB 容器 |
Spring Cloud / Kubernetes |
微服务架构下的思考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 订单服务 │ │ 用户服务 │ │ 支付服务 │ │ (Spring Boot)│ │ (Spring Boot)│ │ (Spring Boot)│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ └──────────────────┼──────────────────┘ │ ┌─────┴─────┐ │ API 网关 │ │ (Gateway) │ └─────┬─────┘ │ ┌─────┴─────┐ │ 客户端 │ │ (浏览器) │ └───────────┘
|
在微服务架构中,每个服务都是独立的、可独立部署的单元。这与 EJB 时代的”单体 EJB 容器”有着本质区别。微服务强调的是服务的自治性和去中心化治理,而不是将所有业务逻辑集中到一个厚重的容器中。

踩坑经验
经验一:EJB 2.x 时代的”地狱配置”
EJB 2.x 需要为每个 Bean 编写 Home 接口、Remote 接口、Bean 实现类,还要在 ejb-jar.xml 中配置大量 XML。一个最简单的 Bean 可能需要 4 个文件。EJB 3.0 引入注解后大幅简化,但历史包袱已经太重。
经验二:远程调用的性能陷阱
RMI 的每次调用都需要序列化和网络传输。如果把细粒度的方法(如 getUser().getName())做成远程调用,会产生大量的网络开销。正确的做法是粗粒度接口设计,一次性返回足够的数据。
1 2 3 4 5 6 7 8 9 10 11
| UserRemote user = ejb.findUser(id); String name = user.getName(); String email = user.getEmail(); String phone = user.getPhone();
UserDTO userDTO = ejb.getUserDetail(id); String name = userDTO.getName(); String email = userDTO.getEmail(); String phone = userDTO.getPhone();
|
经验三:类加载器问题
在集群环境中,不同服务器的类加载路径可能不一致,导致 RMI 反序列化时抛出 ClassNotFoundException。解决方案是确保所有节点的 classpath 和依赖版本完全一致。
总结
EJB 作为 Java 企业级开发的先驱,它的 RMI 基础、三种 Bean 类型、事务管理和集群能力,奠定了现代企业级框架的基石。虽然今天大多数项目已经转向 Spring Boot + 微服务架构,但理解 EJB 的原理,能帮助我们更好地理解 Spring 的 @Transactional、@Service、@JmsListener 等注解背后的设计思想。
技术永远在演进,但底层原理相通。学习 EJB,不只是学习一个框架,更是理解”企业级应用应该如何设计”的经典教材。