EJB 发展历程与弊端分析
概述
Enterprise JavaBeans (EJB) 是 Java 企业级开发的核心技术之一,从 1998 年 EJB 1.0 发布到现在的 Jakarta EE,经历了多次重大变革。本文将详细介绍 EJB 的发展历程,分析其弊端,并通过代码示例直观展示问题所在。
EJB 发展历程
1. EJB 1.0 (1998) - 初生阶段
1.1 设计目标
- 简化企业级 Java 应用的开发
- 提供分布式组件模型
- 支持事务、安全、持久化等企业级特性
1.2 核心特性
- Entity Bean: 用于数据持久化
- Session Bean: 用于业务逻辑
- Home Interface: 用于创建和查找 Bean
- Remote Interface: 定义业务方法
1.3 代码示例
// EJB 1.0 的 Entity Bean 示例
public interface UserHome extends EJBHome {
User create(String name, String email) throws CreateException, RemoteException;
User findByPrimaryKey(String id) throws FinderException, RemoteException;
}
public interface User extends EJBObject {
String getName() throws RemoteException;
String getEmail() throws RemoteException;
void setName(String name) throws RemoteException;
void setEmail(String email) throws RemoteException;
}
public class UserBean implements EntityBean {
private String id;
private String name;
private String email;
private EntityContext context;
// 必须实现的生命周期方法
public void ejbCreate(String name, String email) {
this.id = UUID.randomUUID().toString();
this.name = name;
this.email = email;
}
public void ejbPostCreate(String name, String email) {
// 创建后的处理
}
public void ejbActivate() {
// 激活时的处理
}
public void ejbPassivate() {
// 钝化时的处理
}
public void ejbRemove() {
// 删除时的处理
}
public void ejbLoad() {
// 从数据库加载时的处理
}
public void ejbStore() {
// 存储到数据库时的处理
}
public void setEntityContext(EntityContext context) {
this.context = context;
}
public void unsetEntityContext() {
this.context = null;
}
// 业务方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// ... 其他 getter/setter 方法
}
2. EJB 2.0 (2001) - 成熟阶段
2.1 主要改进
- Local Interface: 引入本地接口,提高性能
- Container Managed Persistence (CMP): 容器管理的持久化
- Message-Driven Bean (MDB): 支持异步消息处理
- EJB Query Language (EJB-QL): 查询语言
2.2 代码示例
// EJB 2.0 的 Session Bean 示例
public interface UserServiceHome extends EJBHome {
UserService create() throws CreateException, RemoteException;
}
public interface UserService extends EJBObject {
UserDTO findUser(String id) throws RemoteException;
void createUser(UserDTO user) throws RemoteException;
void updateUser(UserDTO user) throws RemoteException;
void deleteUser(String id) throws RemoteException;
}
public class UserServiceBean implements SessionBean {
private SessionContext context;
public void ejbCreate() {
// 初始化逻辑
}
public void ejbRemove() {
// 清理逻辑
}
public void ejbActivate() {
// 激活逻辑
}
public void ejbPassivate() {
// 钝化逻辑
}
public void setSessionContext(SessionContext context) {
this.context = context;
}
public UserDTO findUser(String id) throws RemoteException {
try {
// 业务逻辑
UserHome userHome = (UserHome) context.lookup("java:comp/env/ejb/User");
User user = userHome.findByPrimaryKey(id);
UserDTO dto = new UserDTO();
dto.setId(id);
dto.setName(user.getName());
dto.setEmail(user.getEmail());
return dto;
} catch (Exception e) {
throw new RemoteException("Error finding user", e);
}
}
public void createUser(UserDTO user) throws RemoteException {
try {
UserHome userHome = (UserHome) context.lookup("java:comp/env/ejb/User");
userHome.create(user.getName(), user.getEmail());
} catch (Exception e) {
throw new RemoteException("Error creating user", e);
}
}
// ... 其他方法
}
2.3 CMP Entity Bean 示例
// EJB 2.0 CMP Entity Bean
public abstract class UserCMPBean implements EntityBean {
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 String ejbCreate(String name, String email) {
setId(UUID.randomUUID().toString());
setName(name);
setEmail(email);
return null;
}
public void ejbPostCreate(String name, String email) {
// 创建后的处理
}
// 容器管理的方法
public void ejbLoad() {
// 容器自动调用
}
public void ejbStore() {
// 容器自动调用
}
// 查找方法
public abstract Collection ejbFindByName(String name);
public abstract Collection ejbFindByEmail(String email);
}
3. EJB 2.1 (2003) - 完善阶段
3.1 主要特性
- Timer Service: 定时任务支持
- Web Services: Web 服务支持
- EJB-QL 增强: 查询语言功能增强
3.2 代码示例
// EJB 2.1 Timer Service 示例
public class ReportServiceBean implements SessionBean {
private SessionContext context;
public void createTimer(long interval) {
TimerService timerService = context.getTimerService();
Timer timer = timerService.createTimer(interval, "Report Timer");
}
@Timeout
public void generateReport(Timer timer) {
// 定时生成报告的逻辑
System.out.println("Generating report at: " + new Date());
}
}
4. EJB 3.0 (2006) - 革命性变革
4.1 重大改进
- 注解驱动: 大量使用注解替代 XML 配置
- POJO 编程模型: 普通 Java 对象
- 依赖注入: 简化依赖管理
- JPA 集成: 使用 JPA 替代 Entity Bean
4.2 代码示例
// EJB 3.0 的 Session Bean
@Stateless
@Remote(UserService.class)
public class UserServiceBean implements UserService {
@PersistenceContext
private EntityManager entityManager;
@EJB
private EmailService emailService;
public User findUser(Long id) {
return entityManager.find(User.class, id);
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void createUser(User user) {
entityManager.persist(user);
emailService.sendWelcomeEmail(user.getEmail());
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void updateUser(User user) {
entityManager.merge(user);
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteUser(Long id) {
User user = entityManager.find(User.class, id);
if (user != null) {
entityManager.remove(user);
}
}
}
// EJB 3.0 的 Message-Driven Bean
@MessageDriven(mappedName = "jms/UserQueue")
public class UserMessageBean implements MessageListener {
@PersistenceContext
private EntityManager entityManager;
public void onMessage(Message message) {
try {
ObjectMessage objectMessage = (ObjectMessage) message;
User user = (User) objectMessage.getObject();
// 处理用户消息
processUser(user);
} catch (JMSException e) {
throw new RuntimeException("Error processing message", e);
}
}
private void processUser(User user) {
// 业务逻辑
entityManager.persist(user);
}
}
5. EJB 3.1 (2009) - 进一步简化
5.1 主要特性
- Singleton Bean: 单例 Bean
- 异步方法: 支持异步调用
- 定时器 API 增强: 更灵活的定时任务
- 打包简化: 简化的部署结构
5.2 代码示例
// EJB 3.1 Singleton Bean
@Singleton
@Startup
public class SystemConfigBean {
private Map<String, String> config = new HashMap<>();
@PostConstruct
public void init() {
// 系统启动时初始化配置
config.put("app.name", "My Application");
config.put("version", "1.0.0");
}
public String getConfig(String key) {
return config.get(key);
}
}
// EJB 3.1 异步方法
@Stateless
public class AsyncServiceBean {
@Asynchronous
public Future<String> processAsync(String data) {
// 异步处理
try {
Thread.sleep(5000); // 模拟长时间处理
return new AsyncResult<>("Processed: " + data);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new AsyncResult<>("Error: " + e.getMessage());
}
}
}
6. EJB 3.2 (2013) - 稳定阶段
6.1 主要特性
- 可移植性增强: 更好的跨容器兼容性
- CDI 集成: 与 Contexts and Dependency Injection 深度集成
- JMS 2.0 支持: 支持新的 JMS 规范
7. Jakarta EE 时代 (2017-至今)
7.1 重大变化
- 命名空间变更: 从
javax.ejb
到jakarta.ejb
- 开源治理: 由 Eclipse 基金会管理
- 模块化: 更灵活的模块化部署
7.2 代码示例
// Jakarta EE 的 EJB
import jakarta.ejb.Stateless;
import jakarta.ejb.TransactionAttribute;
import jakarta.ejb.TransactionAttributeType;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.EntityManager;
@Stateless
public class UserServiceBean {
@PersistenceContext
private EntityManager entityManager;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public User createUser(User user) {
entityManager.persist(user);
return user;
}
}
EJB 的主要弊端
1. 复杂性过高
1.1 配置复杂性
问题描述: EJB 需要大量的 XML 配置文件,配置复杂且容易出错。
代码示例:
<!-- ejb-jar.xml - EJB 2.x 的复杂配置 -->
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>UserService</ejb-name>
<home>com.example.UserServiceHome</home>
<remote>com.example.UserService</remote>
<ejb-class>com.example.UserServiceBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<env-entry>
<env-entry-name>databaseUrl</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>jdbc:mysql://localhost:3306/mydb</env-entry-value>
</env-entry>
<resource-ref>
<res-ref-name>jdbc/UserDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>UserService</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
对比 Spring 的简洁配置:
// Spring 的配置 - 简单明了
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(
"jdbc:mysql://localhost:3306/mydb",
"username", "password"
);
}
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
1.2 接口过多
问题描述: 每个 EJB 需要多个接口,增加了代码复杂度。
EJB 2.x 示例:
// 需要 4 个文件来实现一个简单的 EJB
// 1. Home Interface
public interface UserServiceHome extends EJBHome {
UserService create() throws CreateException, RemoteException;
}
// 2. Remote Interface
public interface UserService extends EJBObject {
User findUser(String id) throws RemoteException;
}
// 3. Bean Implementation
public class UserServiceBean implements SessionBean {
// 实现所有生命周期方法
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext ctx) {}
// 业务方法
public User findUser(String id) {
// 业务逻辑
return null;
}
}
// 4. 异常类
public class UserServiceException extends Exception {
// 自定义异常
}
Spring 的简化实现:
// Spring 只需要一个类
@Service
public class UserService {
public User findUser(String id) {
// 业务逻辑
return null;
}
}
2. 测试困难
2.1 容器依赖问题
问题描述: EJB 必须在容器中运行,无法进行单元测试。
EJB 测试示例:
// EJB 2.x 的测试 - 需要启动容器
public class UserServiceTest extends TestCase {
private UserService userService;
private Context context;
protected void setUp() throws Exception {
// 需要启动 EJB 容器
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
props.put(Context.PROVIDER_URL, "localhost:1099");
context = new InitialContext(props);
UserServiceHome home = (UserServiceHome)
context.lookup("UserService");
userService = home.create();
}
public void testFindUser() throws Exception {
// 测试需要完整的容器环境
User user = userService.findUser("1");
assertNotNull(user);
}
protected void tearDown() throws Exception {
// 清理资源
context.close();
}
}
Spring 的测试优势:
// Spring 的测试 - 无需容器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testFindUser() {
// 直接测试,无需容器
User user = userService.findUser("1");
assertNotNull(user);
}
}
// 或者使用 Mock 进行隔离测试
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
public void testFindUser() {
// 使用 Mock 对象进行测试
when(userRepository.findById("1")).thenReturn(new User());
User user = userService.findUser("1");
assertNotNull(user);
}
}
3. 性能问题
3.1 远程调用开销
问题描述: EJB 2.x 默认使用远程调用,性能开销大。
性能对比示例:
// EJB 2.x 的远程调用
public class ClientService {
public void processUsers() {
try {
// 每次调用都需要网络通信
UserServiceHome home = (UserServiceHome)
context.lookup("UserService");
UserService service = home.create();
// 远程调用 - 性能开销大
for (int i = 0; i < 1000; i++) {
User user = service.findUser("user" + i);
// 处理用户数据
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// Spring 的本地调用
@Service
public class ClientService {
@Autowired
private UserService userService;
public void processUsers() {
// 本地调用 - 性能好
for (int i = 0; i < 1000; i++) {
User user = userService.findUser("user" + i);
// 处理用户数据
}
}
}
性能测试结果:
本地方法调用: ~1 微秒
Spring 本地调用: ~2-5 微秒
EJB 本地调用: ~10-50 微秒
EJB 远程调用: ~1-10 毫秒
3.2 内存占用过大
问题描述: EJB 容器和 Bean 实例占用大量内存。
内存使用对比:
// EJB 2.x 的内存使用
public class MemoryUsageExample {
public void demonstrateMemoryUsage() {
// EJB 容器本身占用大量内存
// 每个 Bean 实例都有复杂的生命周期管理
// 大量的代理对象和包装器
// 启动一个简单的 EJB 应用需要:
// - 容器: ~200-500MB
// - 每个 Bean 实例: ~1-5MB
// - 代理对象: ~0.5-1MB per Bean
}
}
// Spring 的内存使用
public class SpringMemoryExample {
public void demonstrateMemoryUsage() {
// Spring 容器轻量级
// Bean 实例就是普通 Java 对象
// 最小化的代理和包装器
// 启动一个 Spring 应用需要:
// - 容器: ~50-100MB
// - 每个 Bean 实例: ~0.1-0.5MB
// - 代理对象: 按需创建
}
}
4. 部署复杂性
4.1 部署描述符地狱
问题描述: 需要多个配置文件,部署复杂。
EJB 2.x 部署文件:
<!-- 1. ejb-jar.xml -->
<ejb-jar>
<!-- EJB 配置 -->
</ejb-jar>
<!-- 2. application.xml -->
<application>
<module>
<ejb>myapp-ejb.jar</ejb>
</module>
<module>
<web>myapp-web.war</web>
</module>
</application>
<!-- 3. weblogic-ejb-jar.xml -->
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>UserService</ejb-name>
<jndi-name>UserService</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
<!-- 4. jboss.xml -->
<jboss>
<enterprise-beans>
<session>
<ejb-name>UserService</ejb-name>
<jndi-name>UserService</jndi-name>
</session>
</enterprise-beans>
</jboss>
Spring 的简化部署:
// Spring Boot - 零配置部署
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 或者使用传统的 Spring 配置
@Configuration
@EnableWebMvc
@ComponentScan("com.example")
public class WebConfig {
// 配置类
}
5. 厂商锁定
5.1 特定 API 使用
问题描述: 大量使用厂商特定的 API,难以迁移。
厂商特定代码示例:
// WebLogic 特定代码
public class WebLogicSpecificBean implements SessionBean {
public void ejbCreate() {
// WebLogic 特定的初始化
weblogic.ejb.container.InternalSessionContext context =
(weblogic.ejb.container.InternalSessionContext) getSessionContext();
// 使用 WebLogic 特定的 API
context.getWebLogicHome();
}
}
// JBoss 特定代码
public class JBossSpecificBean implements SessionBean {
public void ejbCreate() {
// JBoss 特定的初始化
org.jboss.ejb.plugins.cmp.jdbc.JDBCContext jdbcContext =
(org.jboss.ejb.plugins.cmp.jdbc.JDBCContext) getEntityContext();
// 使用 JBoss 特定的 API
jdbcContext.getDataSource();
}
}
Spring 的厂商无关性:
// Spring 代码 - 厂商无关
@Service
public class UserService {
@Autowired
private DataSource dataSource;
public void processData() {
// 使用标准 JDBC API
try (Connection conn = dataSource.getConnection()) {
// 数据库操作
}
}
}
EJB 发展过程中解决的问题
1. EJB 3.0 解决的配置问题
1.1 注解替代 XML
EJB 2.x 的复杂配置:
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>UserService</ejb-name>
<home>com.example.UserServiceHome</home>
<remote>com.example.UserService</remote>
<ejb-class>com.example.UserServiceBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
</ejb-jar>
EJB 3.0 的简化配置:
@Stateless
@Remote(UserService.class)
public class UserServiceBean implements UserService {
// 业务逻辑
}
1.2 POJO 编程模型
EJB 2.x 的复杂实现:
public class UserServiceBean implements SessionBean {
private SessionContext context;
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext ctx) {
this.context = ctx;
}
public User findUser(String id) {
// 业务逻辑
return null;
}
}
EJB 3.0 的 POJO 实现:
@Stateless
public class UserServiceBean {
public User findUser(String id) {
// 业务逻辑
return null;
}
}
2. EJB 3.0 解决的测试问题
2.1 依赖注入
EJB 2.x 的依赖查找:
public class UserServiceBean implements SessionBean {
private SessionContext context;
public User findUser(String id) {
try {
// 复杂的依赖查找
UserDAO userDAO = (UserDAO) context.lookup("java:comp/env/ejb/UserDAO");
return userDAO.findById(id);
} catch (NamingException e) {
throw new EJBException(e);
}
}
}
EJB 3.0 的依赖注入:
@Stateless
public class UserServiceBean {
@EJB
private UserDAO userDAO;
public User findUser(String id) {
// 直接使用注入的依赖
return userDAO.findById(id);
}
}
3. EJB 3.1 解决的性能问题
3.1 本地接口优化
EJB 2.x 的远程调用:
// 客户端代码
public class Client {
public void processData() {
try {
UserServiceHome home = (UserServiceHome)
context.lookup("UserService");
UserService service = home.create();
// 远程调用 - 性能差
User user = service.findUser("1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
EJB 3.1 的本地调用:
@Stateless
@LocalBean // 本地接口
public class UserServiceBean {
public User findUser(String id) {
// 本地调用 - 性能好
return userDAO.findById(id);
}
}
// 客户端代码
@EJB
private UserServiceBean userService;
public void processData() {
// 直接调用 - 性能好
User user = userService.findUser("1");
}
4. EJB 3.1 解决的部署问题
4.1 简化的打包结构
EJB 2.x 的复杂打包:
myapp.ear
├── META-INF/
│ ├── application.xml
│ └── weblogic-application.xml
├── myapp-ejb.jar
│ ├── META-INF/
│ │ ├── ejb-jar.xml
│ │ └── weblogic-ejb-jar.xml
│ └── com/example/
└── myapp-web.war
├── WEB-INF/
│ ├── web.xml
│ └── weblogic.xml
└── index.jsp
EJB 3.1 的简化打包:
myapp.war
├── WEB-INF/
│ ├── web.xml
│ └── classes/
│ └── com/example/
│ ├── UserServiceBean.class
│ └── User.class
└── index.jsp
总结
EJB 的发展历程反映了 Java 企业级开发技术的演进过程:
主要成就
- 标准化: 建立了企业级 Java 开发的标准
- 功能完整: 提供了完整的企业级特性支持
- 持续改进: 从 EJB 1.0 到 Jakarta EE 的持续优化
主要问题
- 复杂性过高: 配置复杂,学习曲线陡峭
- 测试困难: 容器依赖,难以单元测试
- 性能问题: 远程调用开销,内存占用大
- 部署复杂: 配置文件多,部署困难
- 厂商锁定: 特定 API 使用,难以迁移
解决方案的演进
- EJB 3.0: 注解驱动,POJO 模型,依赖注入
- EJB 3.1: 本地接口,异步方法,简化部署
- Jakarta EE: 开源治理,模块化,现代化
EJB 的发展历程为后来的轻量级框架(如 Spring)提供了宝贵的经验和教训,推动了 Java 企业级开发技术的不断进步。
本文档基于 EJB 规范的历史版本编写,代码示例仅用于说明概念,实际使用时请参考最新的 Jakarta EE 规范。