IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  WebSphere  >

从 JBoss 移植 EJB 3 应用到 WebSphere Application Server V6.1 Feature Pack for EJB 3.0 的最佳实践

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 中级

冯 月利 (fengyuel@cn.ibm.com), 软件工程师, IBM
张 俊 (junzhang@cn.ibm.com), 软件工程师 , IBM
王 亮 (wanglcdl@cn.ibm.com), 软件工程师, IBM 软件开发中心

2008 年 4 月 09 日

IBM WebSphere Application Server V6.1 Feature Pack for EJB 3.0(以下简称 EJB 3.0 FP)是基于 IBM WebSphere Application Server V6.1 支持 EJB 3 规范的应用服务器。凡是符合 EJB 3 规范的应用都可以部署 EJB 3.0 FP 上。然而,各个应用服务器对 EJB 3 规范的实现稍有不同,本文主要介绍了从 JBoss 移植 EJB 3 应用到 EJB 3.0 FP 的几个最佳实践。

介绍

EJB 3 规范自发布之日起就受到了不少开发者的青睐,原因在于其基于注释和 POJO 的思想简化了 EJB 的开发。相对于 EJB 2.1,基于注释的 EJB 3 代码更加简洁,POJO 的思想使得开发出来的 EJB 更像普通的 Java Bean。主要表现在:

  • 使用 @Stateless, @Stateful, @Entity 等注释声明某个类(接口)是 EJB(EJB 接口)。比如:EJB 3 中,会话 Bean 无需实现 SessionBean,其接口也无需扩展 EJBObject。
  • 大量使用缺省设置。比如,有状态会话 Bean 中的方法 ejbActivate() 和 ejbPassivate(),如果不改动其缺省实现,则不需要定义之,除非开发人员想覆盖缺省实现,才需要定义这两个方法。
  • 推荐使用依赖注入 (Dependency Injection),通过 @EJB 和 @Resource 等注释直接注入需要引用的对象,这种 IoC(Inversion of Control)的思想大大简化了编程。
  • EJB 3 支持 Java EE 5 规范中的注释 (annotation),用户不需要定义部署描述符 ejb-jar.xml。
  • 无需 Home 接口,当然也可以定义 Home 接口以便于跟旧规范进行交互。

EJB 3 的好处和优势并不是本文的重点,所以这里就不一一历数了。想详细了解 EJB 3 的新特性,可参看 [1]。
EJB 3.0 FP 支持 EJB 3 应用,同时向后兼容,支持 EJB 2.1,也支持 EJB 3 和 EJB 2.1 的混合应用。WAS 6.1 上的 EJB 2.1 应用不需要做任何改动,就可以被成功移植到 EJB 3.0 FP 上,这一点仅仅通过 Augment 当前 WAS 6.1 的概要文件到 EJB 3.0 FP 就能做到。本文主要介绍 EJB 3 应用从 JBoss 移植到 EJB 3.0 FP 时的注意事项,各软件提供商对 EJB 3 规范的实现方式和用法稍有不同,比如:JNDI 绑定和命名规则、persistence.xml 文件中的属性定义、EJB 及资源的引用和依赖注入等,所以 EJB 3 应用从 JBoss 移植到 EJB 3.0 FP 时可能需要改动。当然,有些部分,比如拦截器 (Interceptor)、事务、应用程序事务 (Application Transaction) 等,用法大致相同,基本不需要改动。





回页首


移植时应关注的几个最佳实践

本节主要介绍移植时应重点关注的 JBoss 和 EJB 3.0 FP 的几点不同,并提供了相应的最佳实践。


JNDI 绑定和命名规则

EJB 3.0 FP 中的 JNDI 绑定和命名规则旨在能够方便地与其他应用服务器的命名规则相互移植,尽管如此,还是略有不同。


JBoss 的 JNDI 绑定和命名规则

JBoss 有以下两种绑定规则:

  • 缺省绑定

    缺省情况下,会话 Bean 的 JNDI 绑定名称是 :
       * local 接口:<ejbName>/local
       * remote 接口:<ejbName>/remote

    当 EJB 被部署到某个 ear 中后,缺省绑定名称中会以 ear 包的名称作为前缀。比如,ear 文件是 test.ear 时,EJB 的绑定名称是:
       * local 接口:test/<ejbName>/local
       * remote 接口:test/<ejbName>/remote

  • 用户自定义的绑定

    Jboss 中,用户可以通过 @LocalBinding 或 @RemoteBinding 为某个 EJB 自定义 JNDI 绑定名称。注意:@LocalBinding 和 @RemoteBinding 是 JBoss 自提供的扩展注释,而非 EJB 3 规范的标准注释。


EJB 3.0 FP 的缺省绑定和命名规则

EJB 3.0 FP 中,针对 EJB 提供两种命名空间 (namespace):JVM 范围的 ejblocal: 命名空间和全局 JNDI 命名空间。Local 接口和 Local Home 接口(如果有的话)必须绑定到 ejblocal: 命名空间,可被同一应用服务器进程内的其他应用访问得到。Remote 接口和 Home 接口(如果有的话)必须绑定到全局 JNDI 命名空间,从任何地方都能访问之,包括远程客户端或其他应用服务器进程。

ejblocal: 命名空间和全局 JNDI 命名空间是完全分离和独立的。举例来说,绑定在 ejblocal 命名空间的 Local 接口“ejblocal:HelloWorld”跟绑定在全局 JNDI 命名空间的 Remote 接口”HelloWorld”完全不同。

EJB 3.0 FP 中,EJB 容器为每个 EJB 业务接口 (Business Interface) 分配缺省的 JNDI 绑定名称,所以用户无需为每个 EJB 业务接口显式地定义 JNDI 绑定名称。如果不显式地定义绑定名称,EJB 容器会按照以下规则分配绑定名称,这跟以往 WebSphere 对 EJB 的支持稍有不同。

EJB 容器为每个 EJB 的业务接口分配两种缺省的绑定:短缺省绑定(Short Default Binding)和长缺省绑定 (Long Default Binding)。短缺省绑定只引用接口的完整类路径及名称(package.qualified.interface,如 com.ibm.ejb3.test.HelloWorld),而长缺省绑定用企业 Bean 的 component-id 作为前缀,由 # 跟接口的完整类路径及名称一起拼接而成。具体规则如下:

  • 短缺省绑定:
       Local 接口 ejblocal:<package.qualified.interface>
       Remote 接口 <package.qualified.interface>

  • 长缺省绑定:
       Local 接口 ejblocal:<component-id>#<package.qualified.interface>
       Remote 接口 ejb/<component-id>#<package.qualified.interface>

其中,缺省的 component-id 由应用名称、EJB 模块名称和 EJB 的 Bean 名组成,形如:< 应用名称 >/<EJB 模块名称 >/<EJB 的 Bean 名 >,例如:test.ear/myEJB.jar/HelloWorldBean。用户还可以自定义 component-id,覆盖其缺省的 component-id。此外,Remote 接口的长缺省绑定被放在全局 JNDI 命名空间的 ejb 上下文中。


EJB 3.0 FP 的自定义绑定和命名规则

用户可以在 META-INF/ibm-ejb-jar-bnd.xml 文件中为某个 EJB 定义 <simple-binding-name> 或 <component-id> 来自定义 JNDI 绑定,以覆盖其缺省绑定。

<simple-binding-name> 是为 EJB 指定接口绑定的简易做法,适用于 EJB 3.0 的业务接口或者 EJB 3.0 以前的 local home 或 remote home 接口。如果接口是 local 的,则绑定位于 ejblocal: 命名空间,如果接口是 remote 的,则绑定位于应用服务器的全局 JNDI 命名空间的根上下文。见清单 1 中的 A02。

注意:<simple-binding-name> 不能跟 <local-home-binding-name>、<remote-home-binding-name> 或 <interface> 一起使用,也不建议将其用于实现多个业务接口的 EJB。如果非要将其用于实现多个接口的 EJB,为避免二义性,EJB 容器会自动在 simple-binding-name 后追加“#<package.qualified.interface.name>”来区分该 EJB 的各个接口。但这样做并不是最佳实践,会增加 EJB 容器的负担,所以应尽量避免,不用 simple-binding-name,而用 <interface> 为每个接口单独定义绑定。见清单 1 中的 A03。

<component-id> 可单独使用 ( 见清单 1 中的 A01),或者跟 <interface class=… binding-name=…> 一起联合使用,当联合使用时,那些被显式定义了绑定的接口使用这些自定义的值:<component-id>#<binding-name>,而那些未被显式指定绑定的接口,将会使用用户定义的 <component-id> 和缺省的 <package.qualified.interface>,即 <component-id>#<package.qualified.interface>。

注意:<component-id> 不能与 <simple-binding-name> 一起使用,因为 <simple-binding-name> 应用于当前 EJB 的所有接口,也就是说,用了 <simple-binding-name> 后,所有接口都不能用缺省绑定了。而 <component-id> 只是替换了长缺省绑定中 # 以前的部分。


清单 1: EJB 3.0 FP 的自定义绑定和命名规则
                
-----------------------------------ibm-ejb-jar-bnd.xml-----------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar-bnd
xmlns="http://websphere.ibm.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee 
http://websphere.ibm.com/xml/ns/javaee/ibm-ejb-jar-bnd_1_0.xsd" version "1.0">
<session name="A01" component-id="w1/welcome "/>
<session name="A02" simple-binding-name="ejb/A02"/>
<session name="A03">
 <interface class="com.ibm.ejb3.test.welcome" binding-name="ejblocal:session/wlc"/>
 <interface class=”com.ibm.ejb3.test.goodbye” binding-name=”ejblocal:session/gb”/>
</session>
</ejb-jar-bnd>
-----------------------------------------------------------------------------------------

清单 1 中,对 A01 实现的所有接口而言,自定义的 component-id,会覆盖缺省的 component-id(< 应用名称 >/<EJB 模块名称 >/<EJB 的 Bean 名 >)。所以,该 Bean 上的 Local 接口绑定名称为:ejblocal:w1/welcome#<package.qualified.interface.name>,而其所有 Remote 接口绑定为:ejb/w1/welcome#<package.qualified.interface.name>。

对 A02 而言,如果它只有一个业务接口,则当这个接口是 Local 接口时,其绑定名称为 ejblocal:ejb/A02,否则,当这个接口时 Remote 接口时,其绑定名称为:ejb/A02。注意:如果 A02 这个 Bean 实现了多个业务接口,仅仅一个 simple-binding-name 会产生二义性。这种情况下,EJB 容器通过自动在 simple-binding-name 后追加“#<package.qualified.interface.name>”来区分 A02 的各个接口。

对 A03 而言,其业务接口 com.ibm.ejb3.test.welcome 的 <binding-name> 为 ejblocal:session/wlc,表明 com.ibm.ejb3.test.welcome 被绑定在 ejblocal:session/wlc,并且该接口被认定为 Local 接口,如果它不是 Local 接口,会产生错误。同理,业务接口 com.ibm.ejb3.test.goodbye 也一样。注意:不必重新自定义所有的绑定,那些未被定义的将继续使用缺省绑定。如果 A03 还有其他业务接口,则所有这些其他业务接口都使用缺省绑定。

除此之外,必要时,EJB 3.0 FP 还支持用 <local-home-binding-name> 和 <remote-home-binding-name> 等属性定义 Local Home 和 Remote Home 接口的自定义 JNDI 绑定。

小提示: 应用部署到 EJB 3.0 FP 环境中后,通过查看 SystemOut.log 中的标识为“CNTR0167I”的信息了解 EJB 模块的实际绑定。


EJB 3.0 FP 中解决命名冲突的最佳实践

EJB 3.0 FP 中,如果未作任何特殊设置,使用的是短缺省绑定。当某应用中同一包路径下的两个或多个 EJB 实现同一个业务接口时,使用短缺省绑定时会产生二义性,即这些 EJB 的业务接口 JNDI 绑定名称会完全相同,进而发生冲突,当部署到 EJB 3.0 FP 时,会出现 NameAlreadyBoundException 的异常,从而导致 EJB 模块无法启动。

-----------------------------------------------------------------------------------------
[07-8-8 16:17:06:735 CST] 00000022 EJBContainerI E WSVR0040E: myEJB.jar [class com.ibm.ws.runtime.component.DeployedEJBModuleImpl] 的 addEjbModule 失败
javax.naming.NameAlreadyBoundException: com.ibm.websphere.ejbcontainer/test/myEJB.jar/EJBFactory [Root exception is org.omg.CosNaming.NamingContextPackage.AlreadyBound: IDL:omg.org/CosNaming/NamingContext/AlreadyBound:1.0]
at com.ibm.ws.naming.jndicos.CNContextImpl.doBindIOR(CNContextImpl.java:2457)
……
Caused by: org.omg.CosNaming.NamingContextPackage.AlreadyBound: IDL:omg.org/CosNaming/NamingContext/AlreadyBound:1.0
-----------------------------------------------------------------------------------------

此时,可尝试以下两种方案:

方案一: 禁用短缺省绑定而仅使用长缺省绑定即可消除二义性!如果使用长缺省绑定,component-id 里包含 <EJB 的 Bean 名 >,虽然 # 后面的 <package.qualified.interface>(即短名)一样,拼起来就能区分这些不同的 EJB 所实现的接口了,从而避免了冲突。

禁用短缺省绑定的方法如下:

  1. 打开管理控制台,进入“服务器 -> 应用程序服务器 ->(当前应用服务器 , 如 server1)”;
  2. 在服务器基础结构下,进入“Java 和进程管理 -> 进程定义 -> Java 虚拟机 -> 定制属性”,新建以下属性,并保存之。
       名称:com.ibm.websphere.ejbcontainer.disableShortDefaultBindings
       值:* 或 < 应用名称 >
    其中,* 代表此设置适用于该服务器上的所有应用。如果只有某个应用需要禁用短缺省绑定而使用长缺省绑定,则值应设为该应用名称,例如:当应用名称为 test.ear 时,此值应设为 test.
  3. 重启服务器使该设置生效。

这样,禁用短缺省绑定,而仅采用长缺省绑定之后,即使应用中同一包路径下的两个或多个 EJB 实现同一个业务接口,其 JNDI 绑定名称也能相互区分,进而不会发生冲突了。

方案二: 使用自定义的绑定名称。即为这些实现同一接口的 EJB 定义不同的绑定名称,或者一些使用自定义绑定名称,其他使用缺省绑定,只要 JNDI 绑定名称不发生冲突即可。

注意: 以上两种方案仅提供参考,用户应结合实际应用及运行时环境,找出问题根源。


消息驱动 Bean 的移植

通常,消息驱动 Bean(Message Driven Bean, MDB)作为一个服务端点,监控 JMS destination( 即 queue 或 topic) 是否有新消息到来,进而处理新消息,并返回处理结果(即响应)给 JMS 客户端。

EJB 3 规范中,@MessageDriven 用来标记 MDB,@ActivationConfigProperty 指定该 MDB 所监控的 destination 及其类型,这一点 EJB 3.0 FP 和 JBoss 相同。见清单 2。


清单 2: @MessageDriven 和 @ActivationConfigProperty 的用法
                
-----------------------------------------------------------------------------------------
@MessageDriven(activationConfig=
{
 @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Que
 ue"),@ActivationConfigProperty(propertyName="destination", propertyValue="queue/trade")
})
public class TradeBean implements MessageListener {
 public void onMessage(Message msg){
 ......
 }
......
}
-----------------------------------------------------------------------------------------

此外,JBoss 和 EJB 3.0 FP 包括以下几点不同:

  1. JBoss 中,如果指定的 destination( 即清单 2 中的 queue/trade) 不存在,JBoss 容器在部署该 MDB 时会自动创建该资源。而 EJB 3.0 FP 中,我们需要在管理控制台或使用 wsadmin 脚本创建该资源,WAS 不会自动创建该资源。
  2. JBoss 有缺省的 ConnectionFactory 可供使用。EJB 3.0 FP 中,我们需要为 queue/trade 创建 queueConnectionFactory。此外,MDB 用到的其他所有资源,包括 Bus, Destination, ActivationSpecification,都需要在管理控制台或使用 wsadmin 脚本创建。以下是 WAS 环境下所有可能需要创建的资源:
    • Destination:
         Identifier=trade, Type=queue
    • Bus:
         Name=testBus, destinations=trade, Bus members=fengyl03Node01:server1
    • Queue connection factories:
         Name=QCF1, JNDI name=jms/QCF1, Provider=Default messasging provider, Bus name=testBus
    • Queue:
         Name= trade, JNDI name=queue/ trade, Provider=Default messaging provider, Bus name= testBus, Queue name= trade
    • Activation Specifications:
         Name=testAS, JNDI name=eis/testAS, Provider=Default messaging provider, Destination type=Queue, Destination JNDI name=queue/ trade, Bus name= testBus
  3. 此外,EJB 3.0 FP 中,MDB 所使用资源的绑定都需要在 /META-INF/ibm-ejb-jar-bnd.xml 中定义,见清单 3。

    清单 3: MDB 引用资源绑定的定义
                            
    --------------------------------ibm-ejb-jar-bnd.xml--------------------------------
    ......
    <message-driven name="TradeBean">
     <jca-adapter activation-spec-binding-name="eis/testAS" 
     destination-binding-name="queue/trade" />
     <message-destination-ref name="jms/QCF1" binding-name="jms/QCF1"/>
    </message-driven>
    </ejb-jar-bnd>
    ----------------------------------------------------------------------------------
    

经过上述修改,JBoss 上的符合 EJB 3 规范的 MDB 就能被顺利移植到 EJB 3.0 FP 上。


对象关系型数据库映射和 persistence.xml

JBoss 的 EJB3 实现中 , 使用 Hibernate EntityManager 和注释 (Annotation) 作为数据持久化机制,使用 POJO 的方式实现实体 Bean。

EJB 3.0 FP 中,使用“OpenJPA POJO+ 注释”的方式实现实体 Bean。OpenJPA 是 Apache 组织的一个 Java EE 持久层开源项目,它实现了 EJB 3 中的 JPA(Java Persistence API) 规范,为开发者提供功能强大、使用简单的持久化数据管理框架。OpenJPA 既可以作为独立的 POJO 持久层框架使用,也可以与所有符合 EJB 3.0 标准的容器或者其它轻量级框架相集成。

基于 POJO 实现的实体 Bean 应该叫实体类 (Entity Class),而不是“实体 Bean”。但基于习惯,人们往往还愿意称之为实体 Bean。我们这里也暂时叫做实体 Bean。实体 Bean 对应的是关系型数据库中的表,EJB 3.0 FP 关于实体 Bean 的用法跟 Hibernate 有点相似,它通过注释,一个简单的 POJO 实体 Bean 可以对应数据库中的一张表,使用 EntityManager API,可以方便地对数据库表中的数据进行查询、添加、更改和删除。有时,甚至可以通过在 persistence.xml 文件为数据源设置某些属性,自动创建实体 Bean 所对应的数据库表。这一点下面会讲到。总之,EJB 3 实体 Bean 的开发跟 EJB 2.1 及以前相比,简化了许多。其简化的方方面面不是本文的重点,所以这里就不一一赘述了。

实体 Bean 相关注释的用法,JBoss 和 EJB 3.0 FP 中几乎相同。包括:

  • Object/Relational Mapping with Entity Bean and EntityManager Basics: @Entity, @Table, @DiscriminatorColumn, @DiscriminatorValue, @Id, @GeneratedValue, @ManyToOne, @JoinColumn, @PersistenceContext, em.persist(), em.find()
  • Search Database via EJB Queries: em.createQuery()
  • Update and Synchronize the Database: em.createQuery(), em.flush()
  • Entity Bean Life Cycle:@PrePersist, @PostPersist, @ProRemove, @PostRemove, @PreUpdate, @PostUpdate, @PostLoad
  • Configure Persistence Context: persistence.xml, @PersistenceContext

不同的是 persistence.xml 文件中的数据源及其属性定义。
  • JBoss 中,假如使用内嵌的数据库 java:/DefaultDS,则 persistence.xml 定义如下(见清单 4):

    清单 4: persistence.xml
                                
    --------------------------------------persistence.xml-----------------------------
    <persistence>
      <persistence-unit name="test">
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
        <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
      </persistence-unit>
    </persistence>
    ----------------------------------------------------------------------------------
    

    属性 hibernate.hbm2ddl.auto 的值为 create-drop 时,表示我们不需要创建表,JBoss 会根据实体 Bean 对表的映射关系,在服务器启动时自动创建实体 Bean 对应的表,而服务器停止时自动删除这些表。

  • EJB 3.0 FP 中,无论使用何种数据库,在 persistence.xml 设置以下属性,JPA(Java Persistence API) 就会为实体 Bean 自动创建数据库表了。
        <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />

  • 如果您的实体 Bean 中有 @Id 和 @GeneratedValue 注释的属性(即数据库表的 Id 列),表明该 Id 列是自动产生并递加的。如下:

       ---------------------------------------
        @Id
       @GeneratedValue
            public int getId() {
        return id;
       }
       ---------------------------------------

    目前,在 EJB 3.0 FP 中,对于这种情况,除了定义 jta-data-source,我们还需要定义一个 non-jta-data-source( 见清单 5)。jta-data-source 用于常规的数据库访问,non-jta-data-source 用于访问数据库中那些自动生成的列。

    清单 5: non-jta-data-source 的使用
                                
    ---------------------------------persistence.xml-----------------------------------
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi=
    "http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation=
    "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/
    persistence_1_0.xsd">
     <persistence-unit name="tradeDB"> 
     <jta-data-source>jdbc/tradeDB</jta-data-source>
     <non-jta-data-source>jdbc/nonJTATradeDB</non-jta-data-source>
     <class>com.ibm.ejb3.trade.Consumer</class>
     <class>com.ibm.ejb3.trade.Item</class>
     <class>com.ibm.ejb3.trade.Order</class>
     <class>com.ibm.ejb3.trade.WareHouse</class>
     <exclude-unlisted-classes>true</exclude-unlisted-classes>
     <properties>
     <property name="openjpa.jdbc.SynchronizeMappings"
     value="buildSchema(ForeignKeys=true)" />
    	 </properties>
     </persistence-unit>
    </persistence>
    -----------------------------------------------------------------------------------
    

    这样,在 EJB 3.0 FP 上,我们需要创建两个数据源:

    • jta-data-source:jdbc/tradeDB
    • non-jta-data-source: jdbc/nonJTATradeDB

    它们共用一个后台数据库:tradeDB。此外,non-jta-data-source( 比如:jdbc/nonJTATradeDB) 还需要设置以下定制属性表明它是一个非事务型数据源:

         * name="nonTransactionalDataSource"
         * type="java.lang.String"
         * value="true"

    实际上,使用 non-jta-data-source 只是一个暂时性策略,WAS 7.0 中,即使实体 Bean 中有自动生成的 Id 列,也不需要定义 non-jta-data-source 了。

  • 清单 5 中,如果 <exclude-unlisted-classes> 值为 true,则表示只有 <class> 里列出的实体 Bean 有效,当前 EJB 模块中的其他的实体 Bean,即使有,也不生效。如果 <exclude-unlisted-classes> 值为 false,则当前 EJB 模块中的所有实体 Bean 都是有效的。

移植过程中,注意到以上几点,即可保证实体 Bean 顺利被移植到 EJB 3.0 FP 上。


资源和 EJB 的引用和依赖注入

企业级应用经常会碰到这种情况,某个 EJB 引用另一个 EJB 或资源,或者某个 Web 应用调用一个 EJB,在 EJB 2.1 规范中,我们称之为资源或 EJB 的引用,分别通过 ibm-ejb-jar-bnd.xmi 或 ibm-web-bnd.xmi 来指出所引用资源或 EJB 的绑定。

EJB 3 规范引入了一个新的资源 /EJB 引用模式——依赖注入 (Dependency Injection),可以通过在字段和设置方法上加上注释(@Resource 或 @EJB)注入依赖,是在应用中实现松散耦合的的最佳实践。关于依赖注入的优势,并不是本文的重点,所以这里就不一一赘述了。除了 JBoss 在 @Resource 中使用 mappedName 定义资源名称,而 EJB 3.0 FP 在 @Resource 中使用 name 定义资源名称这一点不同,@Resource 和 @EJB 注释的格式在 JBoss 和 EJB 3.0 FP 中基本一致 ( 见清单 6:EJB 3.0 FP 中的依赖注入 )。


清单 6: EJB 3.0 FP 中的依赖注入

                    
---------------------------------------------
@Resource
SessionContext ctx;

@Resource (name=”jdbc/myTradeDB”)
DataSource myDB;

@EJB (name=”ejb/myHelloWorld”)
HelloWorld hw;
---------------------------------------------

前面讨论过 ,EJB 3.0 FP 中 MDB 所使用资源的绑定都需要在 /META-INF/ibm-ejb-jar-bnd.xml 中定义。同样,对资源和 EJB 的引用或依赖注入也需要在 /META-INF/ibm-ejb-jar-bnd.xml 中定义其绑定。

EJB 3.0 FP 环境下有两种方法可以引用资源和 EJB:

  • 方法一 : 使用依赖注入,既使用 @Resource 或 @EJB 注入资源或 EJB。此时,需要在 ibm-ejb-jar-bnd.xml 中定义所注入的资源 /EJB 的绑定名称 ( 见清单 7)。


    清单 7: EJB 及资源引用的定以及绑定
                                
    ---------------------------------ibm-ejb-jar-bnd.xml-------------------------------
    ......
     <session name="TradeBean">	
     <resource-ref name=”jdbc/myTradeDB” binding-name=”jdbc/tradeDB”/>
     <ejb-ref name="ejb/myHelloWorld" binding-name="ejblocal: test.ear/
     myEJB.jar/HelloWorldBean#com.ibm.ejb3.test.HelloWorld"/>
     </session> 
    </ejb-jar-bnd>
    -----------------------------------------------------------------------------------
    

    如果不定义以上绑定信息,在 EJB 3.0 FP 中,运行时会碰到以下错误:

    -----------------------------------------------------------------------------------------
    [07-8-24 15:59:56:945 CST] 00000028 ResourceProce E CWNEN0017E: Unable to resolve the target of the resource-ref declaration jdbc/myTradeDB to its associated binding location in the global name space.
    ......
    Caused by: com.ibm.wsspi.injectionengine.InjectionException: Failed to process bindings for metadata
    -----------------------------------------------------------------------------------------

  • 方法二: 不采用依赖注入的方法,而是直接使用 SessionContext.lookup(“ejb/myHelloWorld”) 的方法引用某 EJB,同样需要清单 7 中这样的 ibm-ejb-jar-bnd.xml 文件。如果被引用的 EJB 接口是 Local 的,且不想定义 ibm-ejb-jar-bnd.xml 文件,则可以直接在 ejb-jar.xml 中定义 <ejb-link>,其值指向被引用的 EJB 的 Bean 名(见清单 8)。


    清单 8: 在 ejb-jar.xml 中定义 EJB 引用
                                
    -----------------------------------ejb-jar.xml-------------------------------------
    <?xml version="1.0" encoding="UTF-8"?>
    <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/
    XMLSchema-instance" id="ejb-jar_ID" metadata-complete="false" version="3.0" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
     <enterprise-beans>	
     <session>
     <ejb-name>TradeBean</ejb-name>	
     <ejb-local-ref>
     <ejb-ref-name>ejb/myHelloWorld</ejb-ref-name>
     <ejb-ref-type>Session</ejb-ref-type>
     <local>com.ibm.ejb3.test.HelloWorld</local>
     <ejb-link>HelloWorldBean</ejb-link>
     </ejb-local-ref>		
     </session>
     </enterprise-beans>
    </ejb-jar>
    -----------------------------------------------------------------------------------
    


EJB 3 应用的安全性

对企业级应用来说,安全性至关重要。EJB 3 规范使得用户通过简单的注释就能控制哪些角色有权访问某一个方法。比如:@RolesAllowed 注释用于指定允许访问某个方法的角色,而被 @PermitAll 注释的方法允许任何人访问。

关于 @RolesAllowed 和 @PermitAll,JBoss 和 EJB 3.0 FP 中的用法相同,用户移植时不需要做任何改动。

关于用户注册这部分,Jboss 对 EJB 3 规范做了扩展,提供了自有的注释 @SecurityDomain 和 SecurityAssociation 对象。@SecurityDomain 注释为应用指定一个安全域 , 该安全域告诉容器密码和用户角色列表的类型和位置。SecurityAssociation 对象可用于存放当前用户的信息,包括用户名、密码等,用户 logout 时调用 SecurityAssociation.clear() 即可安全清除当前用户的信息,成功注销。

@SecurityDomain 的功能即等同于 EJB 3.0 FP 中的用户注册方式的选择。而 SecurityAssociation 存放和读取用户信息及 logout 的功能,在 EJB 3.0 FP 中,可以分别通过设置过滤器 (Filter) 过滤用户信息 ( 作为 Session 属性被存放和读取 ),及 ibm_security_logout 方法完全 logout。当然,该方法仅提供参考。





回页首


移植时不需要改动的模块

上面几节讲述了从 JBoss 移植 EJB 3 应用到 EJB 3.0 FP 时需要特别注意的,两个应用服务器不大相同的地方。此外,还有一些 EJB 3 方法,在两种应用服务器中用法相同,移植时无需改动。


拦截器 (Interceptor)

EJB 3 应用中,可以用注释 @AroundInvoke,@Interceptors 为会话 Bean 或消息驱动 Bean 及其方法设置拦截器。

关于 JBoss 中拦截器的使用方法可参阅相关文档。EJB 3.0 FP 通过 @AroundInvoke,@Interceptors 定义和使用拦截器的方法与 JBoss 相同。您的 EJB 3 应用中拦截器的定义和使用部分,不需要做任何修改即可在 EJB 3.0 FP 环境中正常运行。此外,如果不用注释,用户还可以在 EJB 部署描述符中定义 <around-invoke>、<interceptor> 和 <interceptor-binding>,其中 <around-invoke> 用于标识某方法为拦截器方法,<interceptor> 用于标识某类是拦截器类,而 <interceptor-binding> 用于为某个 Bean 或方法指定所需的拦截器。


事务 (Transaction)

EJB 3 应用中,可以通过 @TransactionAttribute 来设置 EJB 方法的事务类型(见清单 9),可设置的类型包括以下五种:

  • REQUIRED
  • MANDATORY
  • REQUIRESNEW
  • SUPPORTS
  • NOT_SUPPORTED

清单 9: @TransactionAttributed 的用法
                    
-----------------------------------------------------------------------------------------
@Stateless
public class TradeBean implements Trade{
 @PersistenceContext
 protected EntityManager em;
 ......
 @TransactionAttribute(TransactionAttributeType.REQUIRED)
 public void trade (int fundId, int customerId, int quantity) throws Exception {
 ...... }
} 
-----------------------------------------------------------------------------------------

一般,当数据库操作发生错误,或应用程序出错时,事务会回滚 (Rollback),用户可以通过注释 @ApplicationException 自定义一个应用程序异常,并且通过属性 rollback 的值决定该异常错误是否导致回滚 ( 见清单 10)。

清单 10: @ApplicationException 的用法
                    
-----------------------------------------------------------------------------------------
@ApplicationException(rollback=true)
public class TransException extends Exception {
 public TransException () { }
}
-----------------------------------------------------------------------------------------

上述用 @TransactionAttribute 设置事务类型和用 @ApplicationException 创建用户自定义的异常错误,JBoss 和 EJB 3.0 FP 中的用法相同。您的 EJB 3 应用中的这部分,不需要做任何修改即可在 EJB 3.0 FP 环境中正常运行。


应用程序事务 (Application Transaction)

上节讲述了 EJB 3 应用的标准事务,它们是基于线程的。应用程序事务是对标准事务的扩展,是针对包含多个线程的整个应用程序的。

有时候,用户不希望每个线程都有一个事务,不希望每次线程结束都要提交事务,而是希望在调用了一系列线程之后,一次提交所有更改,比如说购物车应用,只需要在用户提交所有订单时,才提交事务,此时就需要应用程序事务。

要使用应用程序事务,必须指定 @PersistenceContext 的 type 是扩展类型 PersistenceContextType.EXTENDED,而且要将每个 EJB 方法的事务类型设置为 NOT_SUPPORTED,即不使用标准事务类型,而使用扩展的应用程序事务。实体管理器(EntityManager)只有当应用结束时才使用 em.flush() 一次性提交修改。

关于 EJB 3 应用程序事务,JBoss 和 EJB 3.0 FP 支持同样的用法,所以移植这部分功能模块时,不需要做任何修改即可在 EJB 3.0 FP 环境中正常运行。





回页首


EJB 3.0 FP 中的注意事项


注释和部署描述符的优先级别

EJB 3.0 FP 中,EJB 3 模块可以没有部署描述符 ejb-jar.xml,而利用注释定义所有元数据。比如,用 @Stateless 定义某类为无状态会话 Bean,@Entity 定义某类为实体 Bean 等。也就是说,原来 ejb-jar.xml 中定义的内容,可以通过一个个分散在代码中的注释来定义。所以不需要再定义 ejb-jar.xml 文件了。

当然,根据需要,也可以选择注释和 ejb-jar.xml 共存的情况,此时,如果注释和部署描述符定义了同一个原数据时,部署描述符中的定义具有优先权,会覆盖相应注释部分的定义。


EJB 3 应用的开发环境

IBM 以下几种环境支持编译和开发 EJB 3 应用:

  • 命令行编译环境: 跟以前一样,可以使用 ant、 make、 maven 或类似的工具,且编译前必须保证 <WAS_HOME>/lib/j2ee.jar 在 classpath 中。此外,几乎没有不同。
  • 集成开发环境 (IDE): 比如 Rational Application Developer(RAD)、 WebSphere Application Server Toolkit(AST),要保证 <WAS_HOME>/lib/j2ee.jar 在项目构建路径中,且保证服务器运行时是安装了 EJB 3.0 FP 的版本。
在 IDE 环境下创建 EJB 3 应用时,应该选择创建 Java 项目或 J2EE 实用项目,而不是 EJB 项目。因为如果选择创建 EJB 项目,该项目就会缺省包含一个符合 J2EE 1.4 标准的 ejb-jar.xml 文件,且 IDE 认为是 ejb-jar.xml 是 EJB 项目必须的。而且,如果想将该新建项目包含到某 EAR 项目中去,则将其以实用 jar 的方式导入,且需要修改 EAR 的 application.xml,声明该 jar 是 EJB 模块。比如:

----------------------------------------------------
<module>
<ejb>myEJB3Module.jar<ejb>
</module>
----------------------------------------------------

EJB 3.0 FP 支持不带 application.xml 的 EAR 应用,此时,它根据 Java EE 5 规范定义的规则自动区分出 EAR 包中的每种类型的模块,包括:EJB 模块、Web 模块、应用客户端模块、或实用 Jar 等。


EJB 3.0 FP 的向前兼容性

在完全支持所有 EJB 3.0 规范的同时,EJB 3.0 FP 还向前兼容,支持基于 EJB 2.1 及其之前规范的应用,也支持 EJB 3 模块与 ejb-jar.xml 共存,或仅有 EJB 3 模块的方式。如果 EJB 模块包含了基于 EJB 3 规范的 Bean,则应被声明为 EJB 3 模块,这一点可以通过设置 ejb-jar.xml 到 3.0 级别,或不包含 ejb-jar.xml 做到。如果 EJB 模块是 EJB 2.1 或更早的版本,则不可以使用注释或依赖注入。包含 CMP 实体 Bean 的 EJB 模块也不能被声明为 EJB 3 模块。

EJB 3.0 FP 支持 J2EE 1.4 及其以前的 web 组件——Servlet/JSP,且支持从 Servlet 中依赖注入或引用某 EJB。同时,EJB 3.0 FP 还支持 J2EE 1.4 及其以前的应用客户端模块,且支持从应用客户端依赖注入或引用某 EJB。

如果从 servlet 2.4 的部署描述符 web.xml 或 J2EE 1.4 客户机的部署描述符 application-client.xml 创建对 EJB 3 local 或 remote 业务接口的引用,其 ejb-ref 和 ejb-local-ref 需要 Home 或 Local Home 接口,但是 EJB 3 缺省没有 Home 和 Local Home,此时可以在 web.xml 或 application-client.xml 中定义 <home> 或 <local-home> 并设其值为空。





回页首


结束语

本文重要讲述了从 JBoss 移植 EJB 3 应用到 EJB 3.0 FP 时需要注意的方方面面。EJB 3 规范以其简洁的开发模式必将受到越来越多开发人员的青睐,EJB 3.0 FP for WAS 6.1 实现了 EJB 3 的三个核心规范 (EJB 3.0 核心规范和需求 , JPA 规范和 EJB 3.0 简化的 API 规范 ),其易用性也将在 WAS 7.0 中有所提高。本文旨在抛砖引玉,为日后用户移植其 EJB 3 应用到 WebSphere 应用服务器提供几点参考。



参考资料



作者简介

作者

冯月利目前在IBM中国软件开发中心从事 WebSphere Application Server 系统测试相关工作。您可以通过 fengyuel@cn.ibm.com 与她联系。


张俊目前在 IBM 中国软件开发中心主要从事 WebSphere Application Server 系统测试相关工作。您可以通过 junzhang@cn.ibm.com 与他联系。


作者

王亮 IBM 软件开发中心 软件开发工程师 主要负责WebSphere Application Server中性能监控工具的系统测试工作。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款