【IT168 技术文章】
EJB 历史回顾
勿庸置疑,1997 年推出的 IBM® 原始 EJB 规范是 Java 技术领域最重要的开发成果之一。EJB 和包含 EJB 的 J2EE 应用服务器迅速地应用于企业开发。然而,对 EJB 的批评之声也正如 J2EE 的采用一样快速涌至。在这些批评之中,最主要的抱怨是 EJB 难于理解且开发起来繁琐乏味。
EJB 规范旨在解决一些复杂问题,比如分布式计算、事务管理和数据持久性。复杂问题通常会产生复杂的解决方案。原始 EJB 规范反映了其旨在解决的复杂问题。结果,开发人员在使用 EJB 时体验了很多痛苦的经历。当您了解 EJB 后,可以使用一些复杂的工具简化开发过程。但对于 EJB 新手而言仍然存在一个很重要的学习过程。
EJB 1.0 和 1.1 规范由 Sun Microsystems 公司开发和发布。后续所有的新规范都使用 Java Specification Requests(JSR)创建并使用 Java Community Process(JCP)认可。Java 社区的参与是促使 Java 技术演化的关键因素。JCP 中反映了 EJB 开发人员的痛苦。结果导致了 EJB 3.0 规范的诞生,其最后定案时间为 2006 年 5 月。
EJB 再也不是仅限于 “解决” 复杂问题,而是 “通过简单便捷的开发” 解决这些问题。EJB 3.0 重点关注的是使初始开发更加简单、使应用程序更易于维护。在本文中,您将了解到 EJB 3.0 相对 EJB 2.1 所带来的简化。首先,我们来考察一下 OpenEJB,它是一个 EJB 实现,也是 Geronimo 的基础之一。
OpenEJB —— EJB 1.1
OpenEJB 问世于 2000 年,其创建者是 David Blevins 和 Richard Monson-Haefel。Blevins 也是 Geronimo 的创建者之一,而 OpenEJB 是 Geronimo 中 EJB 实现的首选。OpenEJB 是 EJB 1.1 规范的第一批开源实现之一。它直接提供了一个远程会话 bean 的实现,并使用 Castor 作为它的容器管理持久性(CMP)实体 bean 的实现。在最开始时,OpenEJB 主要关注的是简化开发人员的工作。例如,OpenEJB 允许使用嵌入式容器(甚至是嵌入式数据库)以简化 EJB 单元测试的编写。大多数 EJB 实现对于 EJB 单元测试的处理都很繁琐,因而开发了一些复杂的框架以处理这个问题(JUnitEE 就是一个最著名的框架)。OpenEJB 是 EJB 1.1 的一个绝好的实现 —— 处理速度快而且用户界面友好。但是,它也需要做些自身改进以满足 Geronimo 的需要。
OpenEJB 和 Geronimo —— EJB 2.1
Geronimo 是 J2EE 1.4 规范的一个开源实现。此规范包含了 EJB 2.1 规范。OpenEJB 是一个 EJB 1.1 实现。EJB 2.1 相对于 1.1 增加了很多内容。这些内容包括:为会话 bean 和消息驱动 bean(MDBs)提供了本地接口、在实体 bean 查找器方法中加入了查询语言并支持将会话 bean 公开为 Web 服务。
所幸的是,Geronimo 团队(包括很多 OpenEJB 贡献者)是一个杰出的团队,他们致力于为 OpenEJB 实现所有的 EJB 2.1 特性,从而为 Geronimo 实现 J2EE 1.4 铺平了道路。在此期间,OpenEJB 接收了 EJB 规范的大量复杂内容。例如,可以很好地用于单元测试的可嵌入容器在寻求与 EJB 2.1 兼容时会带来间接的损害。对于所有的 Java 开发人员而言,所幸的是 EJB 3 即将出现。
EJB 3.0
那么 EJB 3.0 有什么惊人之处?了解 EJB 3.0 的最简单方法就是与 EJB 2.1 比较。我们分别使用二者构建一个简单的 EJB。在下面的章节中,我们将构建一个简单的无状态会话 bean,为您提供当前时间。首先,考察使用 EJB 2.1 时的操作方法。
Time EJB —— 近似 EJB 2.1
最好考察一下 EJB 中的复杂性,它们给有经验的开发人员和 EJB 的初学者都带来了很多痛苦。使用清单 1 中所示的简单接口可以描述一个假想的 EJB。
清单 1. 简单的 Time 接口
2
3 import java.util.Date;
4
5 public interface Time {
6 public Date currentTime();
7 }
8
现在将此接口打造成一个 EJB。首先需要重新定义接口,如清单 2 所示。
清单 2. Time EJB 接口
2
3 import java.rmi.RemoteException;
4 import java.util.Date;
5
6 import javax.ejb.EJBObject;
7
8 public interface Time extends EJBObject{
9 public Date currentTime() throws RemoteException;
10 }
11
非常相似,但是需要从根本上改变接口仍然会让人不安。扩展 EJBObject 好像并不坏,因为它只是一个典型的标记接口。最大的麻烦就是需要更改方法签名以声明抛出一个 RemoteException。EJB 2.0 通过添加一个可选的本地接口解决此问题,如清单 3 所示。
清单 3. Time 的本地接口
2
3 import java.util.Date;
4
5 import javax.ejb.EJBLocalObject;
6
7 public interface TimeLocal extends EJBLocalObject{
8 public Date currentTime();
9 }
10
不错,现在没有 RemoteException 了。当然,您仍然创建了两个接口描述同一个服务。您不能让远程接口扩展本地接口(这也许听起来很有道理),因为前者的 currentTime 方法抛出了一个 RemoteException 而后者则没有,原因诸如此类。接口声明似乎有些突兀,但其影响较下面将介绍到的实现内容而言还算轻微。
您可以想像原始接口的一个简单实现,如清单 4 所示。
清单 4. 原始接口的实现
2
3 import java.util.Date;
4
5 public class TimeBean implements Time {
6
7 public Date currentTime() {
8 return new Date();
9 }
10
11 public TimeBean() {
12 }
13 }
14
这对于 EJB 2.1 而言太过简单。清单 5 中给出的是具有相同功能的 EJB 2.1 版本。
清单 5. TimeBean EJB 实现
2
3 import java.rmi.RemoteException;
4 import java.util.Date;
5
6 import javax.ejb.EJBException;
7 import javax.ejb.SessionBean;
8 import javax.ejb.SessionContext;
9
10 public class TimeBean implements SessionBean {
11
12 public void ejbCreate() {
13 }
14
15 public Date currentTime() {
16 return new Date();
17 }
18
19 public void ejbActivate() throws EJBException, RemoteException {
20 }
21
22 public void ejbPassivate() throws EJBException, RemoteException {
23 }
24
25 public void ejbRemove() throws EJBException, RemoteException {
26 }
27
28 public void setSessionContext(SessionContext sessionContext) throws EJBException,
29 RemoteException {
30 }
31
32 public TimeBean() {
33 }
34 }
35
所有这些 ejbXYZ 方法是从哪儿来的?它们都是由 SessionBean 接口托管。它们是生命周期回调方法。在一些复杂场景中,当复杂会话 bean 的生命周期中需要执行大量的处理和簿记时,这些方法有很大的用处。但是,它是一个典型的边角条件(或最坏场景)编程。
EJB 2.1 的任务还未完成。还需要创建 Home 和 LocalHome 接口。开发人员必须提供这些接口给容器使用。例如,清单 6 给出了一个 Home 接口。
清单 6. Time Home 接口
2
3 import java.rmi.RemoteException;
4
5 import javax.ejb.CreateException;
6 import javax.ejb.EJBHome;
7
8
9 public interface TimeHome extends EJBHome{
10 public static final String COMP_NAME="java:comp/env/ejb/Time";
11 public static final String JNDI_NAME="Time";
12
13 public Time create() throws CreateException,RemoteException;
14
15 }
16
LocalHome 接口几乎完全相同。它扩展了 EJBLocalHome 而不是 EJBHome,并且其 Create 方法没有抛出 RemoteException。
还有更麻烦的,您还需要为 EJB 编写部署描述符。部署描述符将所有这些连接起来并进一步指示容器如何管理 EJB。清单 7 给出了示例 bean 的一个可能的部署描述符。
清单 7. 部署描述符
2
3 <ejb-jar id="ejb-jar_1" xmlns="http://java.sun.com/xml/ns/j2ee"
4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/
6 j2ee/ejb-jar_2_1.xsd" version="2.1">
7
8 <display-name>Time</display-name>
9
10 <enterprise-beans>
11
12 <!-- Session Beans -->
13 <session id="Session_Time">
14 <display-name>Time</display-name>
15 <ejb-name>Time</ejb-name>
16 <home>org.developerworks.time.TimeHome</home>
17 <remote>org.developerworks.time.Time</remote>
18 <local-home>org.developerworks.time.TimeLocalHome</local-home>
19 <local>org.developerworks.time.TimeLocal</local>
20 <ejb-class>org.developerworks.time.TimeSession</ejb-class>
21 <session-type>Stateless</session-type>
22 <transaction-type>Container</transaction-type>
23 </session>
24 </ejb-jar>
25
希望您现在已经很好地了解了 EJB 所带来的痛苦。EJB 3.0 是如何解决所有这些问题的呢?我们来看一下。