转;http://www.ibm.com/developerworks/cn/websphere/library/techarticles/0912_zhuzq_ejbwas7/index.html
IBM WebSphere Application Server 从 7.0 版本起开始正式支持 EJB 3.0(在之前的 6.1 版本有发布针对 EJB 3.0 的功能部件包)。目前建立于 WAS V7 上的 EJB 3.0 应用并不常见。作者在实际项目中总结了使用 WAS V7 部署 EJB 3.0 应用遇到的一些问题(部分为开发细节)及最佳实践,并试图通过一些简单的例子将这些经验展现给需要在 WAS V7 环境下开发 EJB 3.0 应用的开发者们。
本文建立在如下条件之上:
- 使用 IBM WebSphere Application Server V7.0(WAS V7)作为应用服务器
- 使用 IBM Rational Application Developer for WebSphere Software V7.5 进行开发工作
接口,业务对象与实现逻辑相分离 :
建议将 EJB 客户端以及所有定义的业务对象(Business Objects)放到一个类似依赖库的工程中以便将来可以方便地在其他工程中进行调用。此外这样做也提高了接口和业务对象的可维护性。于是在创建工程时我们选择“Create an EJB Client JAR module to hold the client interfaces and classes”选项(这样由 RAD 自动生成的客户端会被放置于该客户端工程中,将来我们可以将自定义的业务对象也放置于该工程内)。
图 1. 创建客户端 JAR 模块
使用可视化工具生成接口代码 :
如果在添加无状态会话 Bean 的时候选择了“Add bean to Class Diagram”,可以在类图中对 Bean 进行基本操作。如选择该 Bean 的一个方法后在右键菜单中点击“Java EE Tools”— >“Promote Methods”,可以将该方法添加入该 Bean 的客户端接口代码中。
图 2. 生成接口代码
开发企业应用时难免需要和其他组件进行交互,下面将分节讨论 EJB 3.0 组件和其他一些组件的交互方式。
使用 EJB 客户端调用发布到应用服务器上的会话 Bean 往往具有一定的局限性(需要有容器上下文 -Context)。于是很多情况下我们希望将其发布为 Web Service 使其方便调用。
自底向上生成 Web Service:
右键单击需要发布为 Web Service 的无状态会话 Bean,选择“Web Services”— >“Create Web Services”,选择“Bottom up EJB Web Service”( 自底向上由 EJB 发布 Web Service) 以及 EJB 实现 Bean(SLBSampleBean),该实现 Bean 会自动被选择。 确认 Web Service 源工程以及 EAR 工程后,点击“Next”。
接下来的配置中,我们需要关注称为“HTTP router”的配置项。当我们通过 IBM 导出工具将一个会话 Bean 发布为 Web Service 后,工具实际上会生成一个接受对该 Web Service 请求的代理类,运行中它将接收到的 Web Service 调用请求转化为对实际会话 Bean 的调用,并将调用结果以 Web Service 响应的形式返回。由于这个代理是一个 Servlet,因此必须将其置于一个 Web 应用程序中。IBM 导出工具可以帮助我们生成这样一个 Web 应用程序并将代理 Servlet 置于其中,而“HTTP router”配置项的值就是该 Web 应用程序的工程名。
图 3. EJB 3.0 Web Service 的架构
使用激活规范(Activation specifications)取代侦听器端口(Listener port):
激活规范是一组消息属性的配置,通过这组配置可以将 Websphere Application Server 和企业信息系统(Enterprise Information System)通过 JCA-Java™ EE 连接器架构(Java™ EE Connector Architecture)1.5 规范连接集成起来。在 WebSphere Application Server V7 之前的版本(V6)中仅支持采用侦听器端口连接 Message Queue 与消息驱动 Bean,而从 Version 7 开始支持使用激活规范连接 Message Queue,并且侦听器端口已经不再推荐使用,因此在今后的开发中我们更多地使用激活规范来取代之前的侦听器端口。
需要创建一个连接 Message Queue 的激活规范,在管理控制台左侧点击“Resources” “JMS” “Activation specifications”。右边的页面中我们能够看到所有 JMS 激活规范的列表。点击“New”按钮。在接下来的表单中,选择 WebSphere MQ messaging provider,表示创建一个连接到 MQ 的激活规范。设置好目标队列的 JNDI 名和类型(Queue),目标队列是 MDB 的消息来源。
选择“Enter all the required information into this wizard”来设置连接的细节。输入 MQ 服务器上的队列管理器名称。设置好连接细节,特别注意确保连接通道(Connection Channel)的配置正确,该值可以在 MQ 服务器上查看。
图 4. MQ 激活规范的连接配置
以上的操作是在管理控制台中完成的,为了提高环境配置的自动化程度以便在实际项目中提高部署和测试的效率,我们也可以使用 JACL 脚本来完成以上配置(方便批量操作)。以下是一个使用 JACL 脚本的例子:
清单 1. 创建激活规范的 JACL 脚本
$AdminTask createWMQActivationSpec [$AdminConfig getid /Server:server1] {-name "ActSpecSample" -jndiName "ActSpecSample" -destinationJndiName "QSample" -destinationType javax.jms.Queue -qmgrName QueueSample -qmgrHostname localhost -wmqTransportType CLIENT -qmgrPortNumber 1414 -qmgrSvrconnChannel SYSTEM.DEF.SVRCONN} $AdminConfig save
如果希望了解更多关于 JACL 脚本语法及其他细节,请参考相关文档。
建立消息驱动 Bean 和激活规范之间的绑定关系 :
在创建好消息驱动 Bean(MDB)和对应的激活规范后,还需要对二者进行绑定。可以通过绑定文件来实现 MDB 和 MQ 激活规范之间的绑定:该文件的文件名必须命名为 ibm-ejb-jar-bnd.xml,并置于工程的 META-INF 目录下。
图 5. 手动创建 MDB 绑定文件
以下是绑定文件的一个例子,我们需要配置需要绑定的 MDB 的名称以及需要使用的 MQ 激活规范的 JNDI 名。
清单 2. 绑定文件内容示例
请注意以上例子的
标签,该标签完成了将 MDBSampleBean 与激活规范 ActSpecSample 绑定的功能。 不过,我们也有另一种方法来取代使用绑定文件:可以在将 EJB 工程部署到 WAS V7 时,在管理控制台中进行配置绑定。如果对应的 EJB 工程中不包含绑定文件,在部署的过程中,我们可以看到多出的一个步骤(如下图 Step 3)Bind listener for message-driven beans,在该步骤中,我们可以指定需要绑定的激活规范。
图 6. 部署过程中绑定激活规范
类似配置绑定文件,在控制台中我们也需要设置要绑定的激活规范的 JNDI 名。
图 7. 激活规范的配置
完成部署步骤之后,我们创建的 MDB 就可以正常工作了(测试过程略)。
EJB 3.0 中,我们使用 JPA (Java Persistent API)来处理有关持久化相关的操作。作者并不打算在本章节中对 JPA 展开深入讨论,如果需要了解更多有关 JPA 的技术细节,请参考相关文献或使用手册。
不使用 ORM 文件:
在创建工程之前,应该先在 RAD 环境中建立一个到目标数据库的连接(在数据源浏览器视图 -Data Source Explorer View 中创建连接)。在创建 JPA 工程时,选择之前建立的数据库连接,并配置好默认的数据库方案(Schema)。由于 EJB 3.0 为了简化开发,增加了对 Java 注释(annotation)的支持,因此,作者建议不为项目指定一个 ORM(对象关系映射)文件而是使用 Java 注释(annotation)来代替,因此不用激活“Create orm.xml”选项。
使用 IDE 辅助生成代码:
在创建好的 JPA 工程中,我们在“JPA Content”中能够看到一个名为 persistence.xml 的文件,该文件是整个持久化环境的配置文件,维护着大部分 JPA 工程的配置信息,在其上右键单击并选择 Configure Project for JDBC Deployment 来设置其中的 JDBC 相关配置项。完成设置之后将会在 persistence.xml 文件中加入连接相关信息。
接下来,我们使用 JPA 工具由数据库表生成实体(Entity)。右键单击 JPA 工程选择“JPA Tools” “Generate Entities”,接着选择已经配置好的连接以及方案(Schematic)。选择需要生成实体的表,这里我们选择已经创建好的表 TABLESAMPLE,并指定生成的目标目录和包(package)。
流程结束后,对应的包下会生成一个实体类,生成的实体类会在 persistence.xml 文件中注册,如下:
com.ibm.gbsc.entity.Tablesample
EntityManager 是 JPA 的一个重要概念。一个 EntityManager 实例和一个持久化上下文(persistence context)相关联,而持久化上下文是一组具有唯一标识的持久化实体的实例(Instance)的集合,实体实例(Entity Instance)及其生命周期都交由持久化上下文来维护。EntityManager 定义了和持久化上下文交互的接口方法,并且向上层提供 API 用于创建或移除一个持久化的实体实例(Entity Instance),通过主键或其他条件来查找实体实例(Entity Instance)。
EntityManager 实例的获取:
我们可以通过注释(annotation)的方式在会话 Bean 中注入一个 EntityManager 对象,代码如下:
@PersistenceContext(unitName = "JPASample") private EntityManager em;
避免使用 DAO 模型:
当然我们也可以使用 RAD 提供的 JPA 工具(JPA Tools)来生成一种称之为“JPA 管理 Bean(JPA Manager Bean)”的对象来管理实体。这种管理 Bean 采用我们所熟知的 DAO 模型来构建管理体系,在 EntityManager 之上进行了一次封装,并提供基本的 CRUD 方法。但是在企业级应用中并不推荐使用 JPA 管理 Bean 而是使用之前的方法,依赖容器来注入 EntityManger,这样的优点是容器可以帮我们更好地管理和维护 EntityManager,并且能够轻松地将其加入到一个全局事务当中(JPA 管理 Bean 在全局事务中无法使用)。以下方法(会话 Bean 的一个方法)示例了 EntityManager 对象的使用:
public void insertDB(long id) { Tablesample bo=new Tablesample(); bo.setId(id); bo.setName("test"+id); em.persist(bo); }事务是企业应用中非常重要的一环。通常情况下,存在三种事务模型:本地事务模型(Local Transaction Model),编程式事务模型(Programmatic Transaction Model),声明式事务模型(Declarative Transaction Model)。
本地事务模型 :
在本地事务模型中,事务由底层数据库(DBMS)或消息提供者(Message Provider)来管理维护,从开发者的角度来看,只需要管理使用连接(connections)而不需要管理事务。例如我们一般情况下的简单的 JDBC 应用开发。
本地事务模型在小型工程的简单更新操作中能够很好地工作,但是在更加复杂的应用场景下,这种模型会让开发者面对很多问题。一个问题就是在处理复杂逻辑时,面向连接的编程容易造成程序的错误。另一个问题是在一个 XA 全局事务中难以协调各资源的工作。
一般情况下,EJB 工程不使用这样的事务模型,让我们关注另外两种事务模型:
编程式事务模型 :
在编程式事务模型中,开发者不再面向连接而是面向事务进行开发。在 EJB 工程中这类事务模型体现为 Bean 管理的事务(Bean-Managed Transactions--BMT)。
我们可以如下开发来使用编程式事务模型:
UserTransaction txn = sessionCtx.getUserTransaction(); txn.begin(); try { // Some operation // ... txn.commit(); } catch (Exception e) { log.error(e); txn.rollback(); throw e; }
代码中 sessionCtx 是 Bean 的一个私有属性(field),可以通过注释 @Resource 来实现从容器注入。此外,我们还需要在 Bean 的实现类的定义中告诉容器该 Bean 的事务模式是 Bean-Managed 的(同样通过注释):
清单 7. BMT 事务声明
@TransactionManagement(TransactionManagementType.BEAN)
当然,这种事务模型也不局限于 BMT,它同样可以应用于容器内的 Servlet 或者 POJO,在容器中获取一个可操作的事务对象,我们使用如下的代码:
InitialContext ctx = new InitialContext(); UserTransaction txn = (UserTransaction)ctx .lookup("java:comp/UserTransaction");
代码中所使用的 JNDI 名“java:comp/UserTransaction”是在 Websphere v5+ 的环境下的用法,如果你所开发的应用不是部署于这样的环境中,请参考该应用服务器的相关文档。
在 EJB(特别是无状态会话 Bean)中,编程式事务模型存在一个重大的架构上的限制:我们不能在两个都使用编程式事务模式的 Bean 之间传递事务上下文(transaction context)。不过,我们可以将一个使用编程式事务模型的 EJB 或客户端的事务上下文传递给一个使用声明式事务模型的 EJB。
因此在一般情况下,我们仅在调用 EJB 的客户端中使用编程式事务模型。接下来我们将讨论声明式的事务模型。
声明式事务模型 :
在声明式事务模型中,由容器来管理维护事务,这意味着开发者不需要通过写 Java 代码来开始或提交一个事务,取而代之的是使用一定的“声明”告诉容器如何来管理事务。我们可以通过 ejb-jarv.xml 配置文件来实现对容器的声明,同样也可以使用注释或部署描述符来声明使用事务。在 EJB 中,声明式的事务模型体现为容器管理的事务(Container-Managed Transactions--CMT)。
在使用声明式事务模型的代码中,我们仅仅需要使用一个方法 -- setRollbackOnly(),其他都可以通过注释或配置来实现。setRollbackOnly() 方法告诉容器本事务不论接下来进行何种操作,最终都将被回滚。以下是使用声明式事务的 EJB 中一个方法实现的例子:
@TransactionAttribute(TransactionAttributeType.REQUIRED) public void operation() { try { // Some business logic // ... } catch (Exception ex) { ex.printStackTrace(); sessionCtx.setRollbackOnly(); } }
当然,我们还需要使用注释来通知容器本 EJB 是一个容器管理事务(CMT)的 Bean:
@TransactionManagement(TransactionManagementType.CONTAINER)
事务策略的组合:
结合以上各种事务模型的讨论结论以及工程中的实际情况,在大部分情况下,我们在实现 EJB 的时候使用声明式的事务模型(即 CMT),而在调用这些 EJB 的客户端代码中使用编程式的事务模型。
本文通过一些简单的例子对 WAS V7 环境下 EJB 3.0 应用开发容易遇到的问题及解决的最佳实践进行了讲解和说明,主要介绍了 EJB 3.0 应用中对外接口,持久层,事务策略三大部分的内容。相信在工程实践中能够给开发者们提供参考。