技术开发 频道

同时托管 J2EE 应用程序的多个版本

  不同版本的 J2EE 应用程序组件可能需要使用不同版本的外部资源,例如不同版本的数据库结构(这些不同的数据库结构用于不同版本的实体 EJB 组件)。为了能够解决这些外部资源冲突,就要通过资源管理器(例如 JDBC 数据源)来访问这些资源。这样做就能够在编写应用程序代码时不必考虑部署应用程序时的情况,并且这也可以在部署阶段使不同版本的应用程序绑定到不同的数据源。图 9 展示了在部署阶段如何为不同版本的实体 bean 指定数据源。不同版本的 J2EE 应用程序组件(例如访问相同外部数据库的实体 bean)在修改相同的数据表或数据记录的时候会存在同步问题。当然,应该避免这种情况的发生。如果无法避免这种情况,那么就要使用适当的 EJB 缓存策略。

  图 7. 在部署阶段通过 JNDI 名指定外部资源

  应用程序设计问题

  将应用程序版本粒度降低到单个的 J2EE 应用程序组件通常会对应用程序的设计做出改变。通过不同的应用程序类加载器使不同版本的服务器端类(例如 EJB tie 及实现类)得以分离,但是不同版本的客户端类可能需要同一个类加载器来加载。当改变 EJB 组件接口,并且不同版本的 EJB 组件需要同一个客户访问时,这种情况变得尤为突出。为了解决这种情况,可以使不同版本的客户端类(包括接口)分隔到不同的包中。不同版本的 EJB 接口的之间的公共部分可以通过使用相同的父接口抽象出来。另外,可以通过修改 Service Locator模式来访问合适版本的 EJB 本地及远程接口。

  在样本应用程序中,需要做出以下改变以适应附加的字段 accountHolderName:

  Account 实体 bean 需要有一个附加的 CMP 字段,名为 accountHolderName。

  AccountInfo 值对象需要有一个附加的字段和 accountHolderName 的访问器方法。

  CreateAccount servlet 需要能够为 AccountHome.create() 方法提供附加的数据(accountHolderName)。

  Create HTML 页面表单和 CreateAccountJSP 应该有新的 accountHolderName 的文本输入。

  Transfer EJB 组件、Transfer servlet 和 TransferFundsJSP 依赖于 Account EJB 组件,但是它们并不需要使用 accountHolderName 属性。因此,这些组件不受添加这个字段的影响。在与此类似的情况下,可以应用 Open-Closed 原则(请参见 [Martin])以这种方式对 Account EJB 组件的变化进行建模,这样,像 Transfer EJB 这样的组件就不受 Account EJB 变化的影响,除非这些组件会使用后者的新功能。在我们的样本应用程序中,Account EJB 的基本功能可以抽象成一个基本接口(Account),并且提供一个基本实现(AccountBean),这些基本接口及基本实现存放在包 com.ibm.mybank.ejb 中。特定于版本的接口和实现可以扩展基本接口和基本实现。

  不同的包应该用于不同的版本,例如:

  com.ibm.mybank.ejb.v1 可以用于封装 Account EJB 组件在版本 1 中的接口及实现,这个版本的 Account EJB 组件中包含的方法不希望在将来的版本中使用。

  com.ibm.mybank.ejb.v2 可以用来封装改变了的 Account EJB 接口及实现,例如,新字段 accountHolderName 的访问器。

  com.ibm.mybank.ejb 可以用来封装和版本无关的 Account EJB 组件的基本接口,这个包也可能包含其他的 EJB 组件,例如 Transfer EJB,这个组件在后面的版本中不受任何影响。

  每个版本的 Account 本地接口只能在特定于版本的包中,因此其本地接口的 create 方法会返回一个相应版本的新创建的 AccountEJB。图8显示了一个类结构图,其中包含形成 Account EJB 不同版本的抽象。

  CreateAccount servlet 需要创建特定于版本的 Account,以及特定于版本的 AccountValue 对象。因此,在特定于版本的包中需要 CreateAccount servlet 的特定于版本的实现。例如, com.ibm.mybank.web.v1. CreateAccount 应该创建类型为 com.ibm.mybank.ejb.v1. Account 的 Account。TransferFunds servlet 使用 Transfer EJB,而 Transfer EJB 在这两个版本中没有变化。因此,TransferFunds servlet 可以使用两者中任一版本的 Transfer EJB。在部署阶段,改变 Transfer EJB 的版本,就如同将 EJB 引用( java:comp/env/ejb/Transfer )映射到任一版本(也就是说, /ejb/MyBank/v1/Transfer 和 /ejb/MyBank/v2/Transfer 中的任一个)的 Transfer EJB 的 JNDI 一样简单。由于 Transfer EJB 组件不需要使用 accountHolderName,因此它只使用基本接口 com.ibm.mybank.ejb.Account 。

  图 8. MyBank 应用程序的多版本类结构图

  为了确定 Account EJB 接口的适当的版本,Transfer EJB 组件可以使用 AccountServiceLocator( Service Locator模式)。样本 3 给出了 ServiceLocator 接口的例子,并且样本 6 给出了 ServiceLocator 模式的实现,这个实现可以用来确定 Account EJB 的版本 1。在部署阶段可以通过使用一个环境项(如样本 5 所示)将适当版本的 ServiceLocator 实现提供给 Transfer EJB 实现。样本 4 展示了可以如何使 ServiceLocator 用于 Transfer EJB 组件。

  EJB stub、tie 和可串行化值/传递对象组成了 J2EE 应用程序的客户和服务器间的接口。EJB stub 和 tie 是在部署阶段从应用程序代码中的 EJB 接口生成的,可串行化值对象完全是通过应用程序代码来实现的。如果您想将不同版本的客户和服务器组件混合使用并且使其兼容,那么 EJB stub、tie 和可串行化值对象就需要具备版本兼容性。

  样本 3. Account ServiceLocator 接口

1 package com.ibm.mybank.service;
2 import javax.ejb.FinderException;
3 import com.ibm.mybank.ejb.AccountLocal;
4 public interface AccountServiceLocator {
5     AccountLocal findAccountLocal(int accountId) throws FinderException;
6 }

  样本 4. Transfer EJB 组件中用于得到 AccountServiceLocator 适当版本的代码

1 private AccountServiceLocator getAccountServiceLocator()  
2 throws Exception {
3     InitialContext initCtx = new InitialContext();
4     String acctServLocClassName = (String)initCtx.lookup(
5 "java:comp/env/AccountServiceLocatorClassName");
6     return (AccountServiceLocator) Class.forName(
7 acctServLocClassName).newInstance();
8     }

  样本 5. 指定 ServiceLocator 适当版本的环境项

1 <session id="Transfer">
2     <ejb-name>Transfer</ejb-name>
3     <home>com.ibm.mybank.ejb.TransferHome</home>
4     <remote>com.ibm.mybank.ejb.Transfer</remote>
5     <local-home>com.ibm.mybank.ejb.TransferLocalHome</local-home>
6     <local>com.ibm.mybank.ejb.TransferLocal</local>
7     <ejb-class>com.ibm.mybank.ejb.TransferBean</ejb-class>
8     <session-type>Stateless</session-type>
9     <transaction-type>Container</transaction-type>
10     <env-entry>
11         <description>
12 The name of the AccountServiceLocator class
13 </description>
14         <env-entry-name>
15 AccountServiceLocatorClassName
16 </env-entry-name>
17         <env-entry-type>java.lang.String</env-entry-type>
18         <env-entry-value>
19 com.ibm.mybank.service.v1.AccountServiceLocator
20 </env-entry-value>
21     </env-entry>
22 </session>

  样本 6. Account ServiceLocator 接口的版本 1 的实现

1 package com.ibm.mybank.service.v1;
2 import javax.ejb.FinderException;
3 import javax.naming.InitialContext;
4 import javax.naming.NamingException;
5 import com.ibm.mybank.ejb.Account;
6 import com.ibm.mybank.ejb.AccountLocal;
7 import com.ibm.mybank.ejb.v1.AccountKey;
8 import com.ibm.mybank.ejb.v1.AccountLocalHome;
9 public class AccountServiceLocator implements
10              com.ibm.mybank.service.AccountServiceLocator {
11     private AccountLocalHome acctLocalHome = null;
12     
13     public AccountServiceLocator() throws Exception {
14         acctLocalHome = getAccountHome();
15     }
16     
17     public AccountLocal findAccountLocal(int accountId)
18          throws FinderException {
19         AccountKey key = new AccountKey(accountId);
20         return acctLocalHome.findByPrimaryKey(key);
21     }
22     private AccountLocalHome getAccountHome() throws Exception {
23         try {
24             InitialContext initCtx = new InitialContext();
25             Object objref =  
26                       initCtx.lookup("java:comp/env/bank/Account");
27             return (AccountLocalHome) objref;
28         } catch (NamingException ne) {
29             ne.printStackTrace();
30             throw new Exception(
31                 "Error looking up AccountHome object: " +
32                         ne.getMessage());
33         }
34     }
35 }

 

  Java Product Versioning Specification中描述了如何开发向后兼容的可串行化值对象。目前,还没有用来开发向后兼容的 EJB stub 和 tie 的规范。因此,必须使用于每个版本的 J2EE 客户和服务器组件保持兼容。

0
相关文章