讨论
引入 Enterprise JavaBeans ( EJB ) 是为了构建分布式组件。最初 , 该技术承诺可以解决 CORBA
的所有问题并降低其复杂性。作为J2EE的核心,EJB经历了几次较大的修订,并加入了许多特性,因而变得臃肿起来。从一开始,大部分开发人员就非常钟爱EJB,甚至在没有任何意义的情况下也在其应用程序中使用EJB。当项目不能正常扩展,又在使用EJB时,很多开发人员都会责怪EJB。
EJB 开发从来就没有变得更为容易 , 相反 , 随着 EJB 规范的相继发布 , 它还变得越来越复杂了。由于其复杂性和本身庞大的体系,EJB被喻为一头大象。许多开发人员认为EJB就像油炸圈饼外边多的一层糖。在狂热奉行低糖饮食和低碳水化合物的年代,EJB专家委员会别无选择,只能努力提供“低糖”的EJB,以简化EJB的开发。EJB
3.0专家委员会在JavaOne 2004会议期间发布了 EJB 3.0规范 的第一个公开草案,并给出了一个轻量级模型的示例图片。
新的 EJB 模块 给人的第一感觉是看上去很漂亮 , 在本文中 , 我们将讨论 EJB 3.0 如何把自己包装得更为小巧玲珑
, 从而吸引开发人员的眼球。在接下来的另一篇文章里,我们将讨论EJB3.0如何简化持久性模型。
EJB 模型的复杂性
在开始讨论EJB 3.0带来的新特性之前,让我们了解一下当前 EJB 模型的复杂性。
- 当前的 EJB 模型 需要创建若干个组件接口并实现若干个不必要的回调方法。
- 这些组件接口需要实现 EJB Object 或 EJB LocalObject , 且需要处理许多不必要的异常情况。
- EJB 部署描述符复杂 , 且易出错。
- 容器管理的持久性基于 EJB 模型 , 也十分复杂 , 不利于开发和管理。缺乏一些基本功能(如按标准方法使用数据库序列定义主键等),且
EJBQL 十分受限。
- 由于有继承性和多态性方面的约束 , EJB 组件与其源对象并不相同。
- EJB 的主要缺点之一就是不能在 EJB 容器外测试 EJB 模块 ,而且 对开发人员而言 , 要在容器内调试
EJB 是件可怕的事。
- 如果用过 EJB , 您就会知道查找和调用 EJB 的有多复杂了。为了在应用程序中使用 EJB ,您必须了解 JNDI
的每个细节。
简化开发人员视图
如果用过最新的规范开发 EJB , 就会发现开发一个类似于 HelloWorldEJB 这样简单的 EJB 有多困难。您至少需要两个接口、一个bean类和一个部署描述符。大多数开发人员都在想:我要这些干什么?在
Oracle JDeveloper 、 Eclipse 和 XDoclet 等IDE中,开发人员可以轻松地完成这些琐事,不过,在将EJB部署到所选的容器之前,开发人员仍需负责编译这些类并包装部署描述符。
EJB 3.0希望使用以下方法来克服这种复杂性:
- 无需使用接口和部署描述符 , 而是由容器使用元数据标注生成。
- 将 普通 Java 类用作 EJB ,将 普通业务接口用于EJB。
元数据标注
EJB 3.0 对元数据标记的依赖性很强。在 JSR 175 下元数据标记得以标准化 , 且将包含在 J2SE 5.0
中。标注是一种面向属性的编程,与Xdoclet类似。不过 , 与需要预编译的 XDoclet 不同 ,标注是在 编译时由
Java 编译器编译到类中的 ( 取决于如何设置 @Retention ) 。对开发人员而言,标注是类似于public一样的修饰符,可以在类、字段、方法、参数、本地变量、构造函数、枚举及包中使用。可以指定可用于生成代码、归档代码或在运行时提供特殊服务(增强的业务级安全或特定业务逻辑)的属性,从而在Java代码中使用标注。J2EE
1.5(5.0)的目标是用标注来简化开发,因而它会提供自己的一组标注。标注使用@来进行标记,如下所示:
@Author("Debu Panda")
@Bean
public class MySessionBean
EJB 3.0 的目标是为了简化开发 , 因而要使用元数据标注来生成若干类似接口一样的工件 ,要 使用标注而不使用部署描述符。
使用 POJO 和 POJI
按照规范化的术语 , JavaBeans 和接口经常分别被称为 Plain Old Java Objects ( POJO
) 和 Plain Old Java Interfaces ( POJI) 。现在的EJB类和接口将分别类似于POJO和POJI。像home接口之类的不必要工件将不再需要。
开发人员要么必须在 javax.EJBpackage 中实现一个 EJB 接口 ( SessionBean 、 EntityBean
或 MessageDrivenBean ),要么就是 在 bean 实现类中使用标注。可以使用 Stateless 、
Stateful 、 MessageDriven 或 Entity 标注 bean 类。例如 , 若将一个Stateless
EJB 定义为 HelloWorld ,可以这样 定义该 EJB :
@Remote
@Stateless public class HelloWorldBean {
public String sayHello(String s)
{ System.out.println("Hello: "+s; }
}
EJB 的接口可以是远程的 , 也可以本地接口 ,都 不必实现 EJBObject 或 EJBLocalObject
。必须为 EJB 提供业务接口 , 并在 bean 类中实现该接口 ; 或者在部署期间生成该接口。对于Entity bean,接口是可选的;不过对于
SessionBean 和 MessageDrivenDriven ,接口则是必需的。如果没有为session bean实现接口,将会生成一个bean接口。生成的接口类型可以是本地的或远程的,取决于在bean类中使用的标注。从上面的代码示例可以很清楚地看出,
@Remote 是用于为 HelloWorld bean生成远程接口的。如果需要,可以为EJB同时提供远程接口和本地接口。
上面的例子清楚地表明 , 开发人员没有必要进行大量的寻常任务 , 如定义接口和实现回调方法。
生成的接口的名称源自 bean 实现类的名称。生成的接口对开发人员来说的确不错。不过,我认为生成接口并没有多大优势,因为大多数IDE(如Oracle
Jdeveloper)都可动态生成这些接口。
草案中并没有清楚地说明什么是 EJB 查找的客户端要求 ,以及 如何得到调用该 EJB 所需的这些接口。我建议不要使用生成的接口,原因如下:
- 生成的接口的名称将从 bean 名称派生
- 您可能不愿意在生成的接口中公布 EJB 中 的某些方法 , 而默认情况下 , 生成的接口将公开所有的方法。
- 您需要客户端接口来调用 EJB 。
不再需要回调方法
EJB2.1 及以前的版本要求为每个 EJB 实现若干个生命周期方法 , 如 ejbPassivate 、 ejbActivate
、 ejbLoad 、 ejbStore 等 , 即使不需要这些方法 , 也要这样做。例如 , Stateless 会话
bean 不需要 ejbPassivate , 但仍需要在 bean 类 中实现该方法。由于现在的EJB3.0与普通Java类类似,因此实现这些生命周期方法已经不是必需的了。若在EJB中实现回调方法,容器就会调用该方法。
惟一的例外是 Stateful 会话 bean 中的 ejb Remove 方法 , 在 Stateful 会话 bean
中可以使用 Remove 标注来标注 Stateful 会话 bean 业务方法。如果使用此标注 , 它将会在被标注的方法完成
( 正常或异常完成 ) 后提示容器删除 Stateful 会话 bean 实例。例如 , 可以指定以下代码在执行完 checkOut
方法后删除 Stateful 会话 bean 实例。
@Stateful public class Cart {
...
...
@Remove public void checkOut() {
...
}
}
标注与部署描述符对比
如前所述 , EJB 将不再需要部署描述符 , 而将使用标注。部署描述符中的每个属性的默认值都将被选定,开发人员无需指定这些属性,除非要使用默认值以外的值。可以在bean类本身中使用标注来指定这些值。
EJB 3.0 规范为开发人员定义了一组元数据标注 , 如 bean 类型、接口类型、资源引用、事务属性、安全性等。举例来说,假设我们希望对某一特定的EJB进行资源引用,则进行如下定义:
@Resource(name="jdbc/OracleDS", resourceType="javax.sql.DataSource")
J2EE 供应商 ( 如 Oracle 、 BEA 、IBM ) 将在其特定于供应商的部署描述符中添加属性标注 , 开发人员将使用这些标注来避免使用部署描述符。对于开发人员而言,这相当有吸引力,因为没有了他们最讨厌的XML描述符。不过,这也带来一些问题,在使用标记的之前,我们需要多几分谨慎。
- 这妨碍了应用程序可移植性目标的实现 , 因为如果 EJB 使用特定于供应商的部署描述符 , 并且不重新编译 /
重新包装 EJB ,变化 就不会如愿以偿。
- 无需逐个查看 ejb ,部署描述符就为汇编器 / 部署器 ( ejb-jar ) 提供了 EJB 模块的完整视图,它们还按照每一部署的要求调整这些视图。如果描述符不可用或直到部署结束时才生成,那么后果可能会不堪设想。
- 各种工具使用部署描述符在 EJB 模块 中识别 ejb , 当在容器间进行迁移时这会非常有用。
EJB 3.0 规范还提出了一种重写部署描述符中标注的方法。不过,规范中并没有给出重写标记的具体细节。
毫无疑问 , 不再使用部署描述符将使新的开发人员能够更轻松地开展工作 , 但如果使用不当 , 可能会引发管理问题。
简化容器管理的持久性
EJB 3.0 对 CMP 实体 bean 进行了全面的革新 ,以吸引 开发人员的注意力。持久性框架 ( 如 OracleAS
TopLink ) 、开放源码的 Hibernate ) 已成为开发 J2EE 应用程序持久性框架的宠儿,而 实体 bean
由于既 复杂又沉重,已不再受欢迎。 EJB 3.0 采用了一个类似 TopLink 和 Hibernate 的轻量级持久性模型
, 以简化容器管理的持久性 , 而这对开发人员而言无疑很有诱惑力。我们来简单了解一下该实体bean计划,关于持久性改进方面的详细内容,我们将在另一篇文章中讨论。
实体 bean 正在 作为 POJO 而重获新生,实体 bean 也 将不再需要组件接口。现在实体bean将被视为纯粹的对象,因为它也将支持继承性和多态性。
以下是 实体 bean 的源代码 :
@Entity public class Employee{
private Long empNo;
private String empName;
private Address address;
private Hashmap projects = new Hashmap();
private Double salary;
@Id(generate=SEQUENCE) public Long getEmpNo() {
return empNo;
}
protected void setEmpNo(Long empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return EmpName;
}
public void setEmpName(String EmpName){
this.EmpName = EmpName;
}
@Dependent public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Set getProjects() {
return projects;
}
public void setProjects(Set projects) {
this.projects = projects;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
....
}
观察以上代码 , 可以发现此 bean 类是当前 实体 bean 的实体类 , 而不是抽象类。
EJB QL 中对查询功能进行了若干改进 ,并 在 实体 bean 中支持 SQL 查询。提出类似于 Hibernate
的新 EntityManager API ( TopLink ' Session API 的一个简化版本)用于对实体 bean
进行操作 , 即创建、删除和查找 实体 bean 。。
我们将在下一篇文章中仔细探讨提出的 CMP 实体 bean 的细节。
简化 EJB 的客户端视图
使用 EJB ( 即查找和调用)非常复杂 , 即使在应用程序中已经配置了 EJB 。J2EE 1.4和EJB 3.0规范正是要简化EJB的客户端视图。
若现在就想使用 EJB , 则必须在部署描述符中定义 ejb-ref 或 ejb-local-ref , 查找 EJB
然后再调用。若想调用HelloWorld EJB,以下是在当前实现中调用EJB的最简单方法。
首先在部署描述符中定义 EJB 引用 , 如下所示 :
<ejb-ref>
<ejb-ref-name>HelloWorldEJB</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>hello.HelloWorldHome</home>
<remote> hello.HelloWorld</remote>
</ejb-ref>
然后 , 利用以下代码查找 EJB 。必须显式处理 bean 实例的 EJB 查找和创建异常情况。
try
{
Context context = new InitialContext();
HelloWorldHome helloHome =
(HelloWorld)PortableRemoteObject.narrow(context.lookup
("java:comp/env/ejb/HelloWorldEJB"), HelloWorldHome.class);
HelloWorld hello = helloHome.create();
....
}
catch(RemoteException e)
{
System.err.println("System/communication error: " + e.getMessage());
}
catch(NamingException e)
{
System.err.println("Communication error: " + e.getMessage());
}
catch(CreateException e)
{
System.err.println("Error creating EJB instance: " + e.getMessage());
}
对于 环境变量 , EJB 3.0 建议使用另一种方法,即使用 setter 注入来查找和调用 EJB 。
以下代码是使用 setter 注入在另一个 EJB 中查找 HelloWorldEJB 的方法。
@Inject private void setSessionContext(SessionContext ctx)
{
this.ctx = ctx
}
...
myHello = (HelloWorld)ctx.lookup("java:comp/env/ejb/HelloWorldEJB");
仔细研究上面的代码 , 可以发现 setSessionContext 方法用 @Inject 进行了 标注 , 目的是将依赖注入用于该方法。注入的方法将由容器调用,以在该EJB上调用任一业务方法前设置EJBContext。
另一个注入 HelloWorld 会话 bean 的直接例子是只使用 @EJBpublic HelloWorld myHello
, 这将导致 myHello 通过一个 HelloWorld bean 实例注入。
可以使用依赖注入来查找任何类型的环境和资源引用 , 如 DataSource 、 JMS 、 Mail 、 Web 服务
等。
容器外的可测试性和可用性
当前 EJB 开发人员所关心的一个主要问题不仅包括 EJB 开发的复杂性 , 还包括测试这一难题。开发和测试EJB离不开EJB容器,开发人员也必须熟悉最终的部署平台,才能进行测试。对于很多企业开发人员而言,这可能不是主要问题。但对于为多个供应商提供支持的ISV而言,这的确是个问题,他们必须维护多个环境以测试其EJB。EJB
3.0规范承诺要提供在容器外进行测试的能力,但目前在规范的草案中并没有提到细节。
结束语
尽管遗漏了很多包装、组装以及 API 的细节信息 , EJB 3.0 草案中的提议仍然对企业 Java 开发人员充满了诱惑。把这些工作留给容器供应商来做,将有助于减少开发人员面对的复杂问题。这就要看容器供应商如何实施这些工作,并使EJB
3.0成为开发企业应用程序的必然之选了。
参考资料