行莫
行莫
发布于 2025-09-09 / 1 阅读
0
0

EJB 发展历程与弊端分析

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.ejbjakarta.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 企业级开发技术的演进过程:

主要成就

  1. 标准化: 建立了企业级 Java 开发的标准
  2. 功能完整: 提供了完整的企业级特性支持
  3. 持续改进: 从 EJB 1.0 到 Jakarta EE 的持续优化

主要问题

  1. 复杂性过高: 配置复杂,学习曲线陡峭
  2. 测试困难: 容器依赖,难以单元测试
  3. 性能问题: 远程调用开销,内存占用大
  4. 部署复杂: 配置文件多,部署困难
  5. 厂商锁定: 特定 API 使用,难以迁移

解决方案的演进

  1. EJB 3.0: 注解驱动,POJO 模型,依赖注入
  2. EJB 3.1: 本地接口,异步方法,简化部署
  3. Jakarta EE: 开源治理,模块化,现代化

EJB 的发展历程为后来的轻量级框架(如 Spring)提供了宝贵的经验和教训,推动了 Java 企业级开发技术的不断进步。


本文档基于 EJB 规范的历史版本编写,代码示例仅用于说明概念,实际使用时请参考最新的 Jakarta EE 规范。


评论