• 同时托管 J2EE 应使用程序的多个版本
  •  2009/5/17 7:26:34 | 分类:J2EE | 阅读:823
  • 导读:引言应用程序由多种不同类型的组件构成,例如servlet、组件和客户端,这些组件可以封装在不同的模块中。随着应用程序的日趋成熟,也就有必要不断使用新的应用程序功能。普遍的做法是…



  •   引言



      J2EE 应用程序由多种不同类型的组件构成,例如 servlet、EJB 组件和 J2EE 客户端,这些组件可以封装在不同的模块中。随着 J2EE 应用程序的日趋成熟,也就有必要不断使用新的应用程序功能。普遍的做法是通过小规模的增强或者对组件接口(或者其实现)加以改进来获得新应用程序功能,而不是对整个企业应用程序进行大规模的改变。因此,在许多情况下,对每个应用程序组件的不同版本加以支持(同时可以重用其他相关的组件,而这些相关的组件不受新版本变化的影响)是人们所期望的事情。



      为了实现上述功能,有必要对需要升级的应用程序组件进行增量式,而不是使用一种“大手笔”的方法对整个企业应用程序进行更换。要想实现这种增量式,就有必要使同一个应用程序服务器能够支持企业应用程序组件的多个不同的版本。



      本文论述和讲解为了支持在一个 WebSphere Application Server 实例中同时托管 J2EE 应用程序的多个版本所需要解决的关键问题。尽管其中的一些问题可以在部署阶段解决,但是也有一些问题需要在应用程序设计和开发的阶段加以解决。我们在此使用一个小型的 J2EE 应用程序(由 EJB 组件、servlet、JSP 以及 HTML 构成)对如何解决这些关键问题进行了示范。



      有不同的方法可以使企业应用程序从一个版本完全转换到另外一个版本,例如在同一 WebSphere 网络域中使用不同的单元。这些方法不在本文的讨论范围之内,本文只在 EJB 组件、servlet 或静态内容的层面上以较细的粒度论述应用程序的版本。



      同时托管的冲突和问题



      同时托管的 J2EE 应用程序的不同版本需要使用共同的应用程序服务器资源,如类加载器、JNDI 名称空间、应用程序 URL 和其他的外部资源,这些因素会产生冲突。我们将这些冲突列举如下(并且在本文的后面将其进行讨论)。



      类加载冲突

    不同版本的 J2EE 组件类需要加载到同一个应用程序服务器的 Java 虚拟机进程中。通过在 WebSphere 应用程序服务器中使用多种类加载器,可以避免这些冲突。



      servlet 路径冲突

    不同版本的 Web 应用程序组件(例如 servlet、JSP 和 HTML 模块)会有冲突的 URL。这些冲突可以通过对每个版本使用不同的上下文根来解决。



      JNDI 名称空间冲突

    J2EE 应用程序组件可能会引用远程和(或)本地的持久性对象,这可能包括 EJB 本地接口。多个版本的 J2EE 应用程序组件不能在同一应用程序服务器域的同一个 JNDI 名称空间注册相同的名称。解决名称空间冲突的方法是避免在应用程序代码中使用硬编码的 JNDI 名称空间,而依赖于 java:comp/env 名称空间中的应用程序环境项(正如 J2EE 1.2 规范中所定义的)。通过这种方式,在部署阶段可以为不同的版本指定实际的 JNDI 名称空间。



      外部资源冲突

    不同版本的 J2EE 应用程序可能会使用相同的外部资源的不同版本,例如数据库结构。可以通过资源管理器(例如 JDBC 数据源)使不同版本的应用程序使用同一外部资源的不同版本,并且通过应用程序的 java:comp/env 环境来访问资源管理器。通过这种方式,直到部署阶段才将外部资源和使用它的应用程序代码绑定起来,因而可以在部署阶段指定外部资源的不同版本。



      图 1 突出显示了在同时托管两个不同版本的 J2EE 应用程序时发生冲突的部分。

    图 1. 在同一个应用程序服务器上同时托管的不同版本的 J2EE 应用程序之间的冲突。

    同时托管 <a href=J2EE 应用程序的多个版本" />



      另外,除了以上的各种冲突,也要考虑下面的应用程序设计和开发问题:



      应用程序设计问题

    为了能够支持多个版本的分布式 J2EE 组件,在设计组件接口时要特别加以注意,例如 EJB 的远程接口及其对应的实现类、客户端类(包括使用接口的类)。在设计可串行化的或者用于传递对象或值对象(这些对象在组件接口之间传递)的可外化的类(请参见 参考资料)时,也要特别加以注意。

    同一个服务器同时托管不同版本的应用程序组件的一种方法是,将不同版本的应用程序组件类放在不同的 JAR 文件中,并且不同版本的应用程序使用不同的类加载器,但这种方法的价值不大。这种方法只有在所有的版本变化仅仅局限于类的实现时才是一种适用的方法。如果组件接口变化了(例如 EJB 接口),那么相应版本的所有客户端类也要具有不同的版本,即使客户端不会使用任何改动的功能也是如此。因此,对 J2EE 组件接口(例如 EJB 接口)做出很小的改动,就会牵扯到改变大量的客户端类。通过应用面向对象的设计方法来管理单个的 J2EE 组件实现类及接口如何变化,可以更好地对此进行处理。

    使用面向对象方法的其他好处包括可以重用不同版本类的通用功能。使用不同的包名,可以避免不同版本之间的类命名冲突。这也可以允许一个组件访问其他组件的不同版本(例如,同一个 EJB 组件的不同版本可以被同一个引用它的 servlet 支持)。在后面描述的示例 J2EE 应用程序中,我们将看到如何改变组件的接口以便同时托管。



      封装选择

    J2EE 应用程序类可以封装在 EJB 模块( .jar )或 Web 模块( .war )中。封装企业应用程序不同版本的一个最简单的方法就是,将每个版本的所有 EJB 和 Web 模块封装到它们自己的企业应用程序模块中( .ear )。尽管这样做可能不会带来太多的困难,但是这或许不是最好的解决方案,因为企业应用程序模块通常是由大量的 EJB 和 Web 模块组成的,并且所有的组件不会同时从一个版本升级到另一个版本,而实际上可能会是一次升级只涉及到单个的组件。在本文的后面提供了一种封装选择,这种选择可以得到较细粒度的应用程序版本。



      跟踪分布式组件的变化

    EJB JAR 和 WAR 清单文件(manifest file)提供了跟踪不同包版本的方法。可以在运行时利用版本信息来检查 J2EE 应用程序分布式组件之间的兼容性。尽管在运行时使用 J2EE 规范可以表达和检索单个组件的版本信息,但是没有一种方法来表达一个版本模块对另一个版本模块的依赖性。检查和执行这种依赖性必须通过运行时的应用程序逻辑来完成。



      会话对象的不兼容性

    如果应用程序持续使用可串行化的对象,并且会对这些可串行化的对象的类做出改变,那么在做出这些改变的时候要加以考虑。



      样本 J2EE 应用程序



      为了展示如何解决上面列出的问题,我们将考虑一个小型的 J2EE 应用程序,我们将其称作为 MyBank。

    图 2. 样本 J2EE 应用程序:MyBankk

    同时托管 <a href=J2EE 应用程序的多个版本" />



      MyBank 应用程序被封装在 MyBankCMP.ear 文件中,并且由下面列出的 J2EE 组件和模块组成(如图 2 所示):



      EJB 模块(以绿色显示): MyBankCMPEJB.jar

    包名: com.ibm.mybank.ejb ,由以下元素组成:



      Account ——是一个实体 bean,表示一个用户的帐号,并且允许用户查看帐号信息、提款和存款,而且还可以让用户查看以及设置帐号类型。



      Transfer ——是一个会话 bean,这个会话 bean 可以使用户查看帐号余额,并且可以将资金从一个帐号转到另一个帐号。



      AccountValue ——是一个传递对象,这个对象表示从 Account 实体 bean 远程接口获取的帐号信息。



      Web 模块(以黄色显示): MyBankCMPWeb.war

    包名: com.ibm.mybank.web ,由以下 Web 组件组成:



      CreateAccount ——这是一个 servlet,这个 servlet 调用 Account 本地接口的创建操作,并且通过一个新构建的 AccountInfo 值对象来传递这个 servlet。



      Create ——这是一个静态的 HTML 页面,这个页面提供用于接受用户输入的表单,并通过调用 CreateAccount servlet 来创建一个 Account。



      CreateAccountJSP ——这是一个 JSP,用来显示创建帐号操作的结果,并且具有输入表单。



      TransferFunds ——这是一个 servlet,这个 servlet 调用 Transfer bean 中的转帐及查看帐号余额的操作。



      Transfer ——这是一个 HTML 页面,这个页面提供用于接受从一个帐号到另一个帐号转帐的输入表单,并且可以查看帐号余额。



      TransferFundsJSP ——这是一个 JSP 页面,这个页面显示转帐及查看帐号余额操作的结果,并且具有输入表单。



      图 3 中的类结构图显示了应用程序 MyBank 中的类。

    图 3. MyBank 应用程序的类结构图

    同时托管 <a href=J2EE 应用程序的多个版本" />



      在上面的示例场景中,让我们来考虑一下同时托管两个不同版本的 MyBank J2EE 应用程序的问题。我们可以通过添加一个新的帐号持有人名称字段,使我们的应用程序从版本 1 变化到版本 2。在图 2 中,我们用影标出了在此改变中受影响的组件。在随后的部分中,我们将详细讨论因为这种变化而带来的同时托管问题。



      类加载冲突



      J2EE 应用程序组件的各个版本中的相同类的不同版本之间的类加载冲突,可以通过在相同的 WebSphere 应用程序服务器 JVM 进程中使用不同的类加载器来解决。



      图 4 显示了 WebSphere Application Server 的类加载器的层次。(请参见 参考资料。)

    图 4. WebSphere 类加载器层次

    同时托管 <a href=J2EE 应用程序的多个版本" />



      应用程序类加载器负责加载 EJB JAR 文件中的 EJB 模块以及封装在相关的 JAR 文件中的实用程序类。不同的应用程序加载类可以使相同的 EJB 类以及 EJB 类使用的实用程序类的不同版本得以分离。J2EE 应用程序中的 Web 模块通过 WAR 类加载器来加载,这个类加载器是企业应用程序中的应用程序类加载器的一个子类。可以设置使用不同的 WAR 类加载器来加载不同版本的 Web 组件类。可以使用不同的封装选择来分隔不同版本的 J2EE 组件,并且这种做法本身就意味着要使用不同的类加载器。(请参见 封装选择。)



      服务器端组件类的不同版本应该尽量使用不同的类加载器使其分离开来。然而,有时有必要共享客户端类(例如当 EJB stub 属于不同版本的情况),这样可以使不同版本的应用程序访问同一个组件,例如一个 servlet。这种情况可以在应用程序开发阶段通过使用不同的包名来解决。(请参见 应用程序设计问题。)



      servlet 路径冲突



      Web 组件的不同版本可能具有不同的 servlet 上下文根,这就可以使 Web 容器识别出同一个 Web 组件的不同版本,并且可以同时使用不同的 WAR 类加载器加载相同 servlet 类的不同版本。通过这种方式,应用程序版本的粒度可以降到 Web 模块(.war文件)级上。servlet 上下文根是在企业应用程序档案(.ear)文件中的部署描述符中对每个 Web 应用程序进行指定的。样本 1 给出了 EAR 部署描述符指定上下文根的例子。



      静态内容(例如 HTML 页面和 JSP 页面)的不同版本也可以通过 Web 应用程序的不同上下文根来唯一指定。要获得相关的示例,请参见 Testing Multiple Versions of Web Applications。



      样本 1. 在应用程序部署描述符中为 Web 应用程序的不同版本指定不同的上下文根 <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE application PUBLIC
      "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
      "http://java.sun.com/dtd/application_1_3.dtd">
    <application id="Application_ID">
      <display-name>MyBankCMPWebEAR</display-name>
      <module id="WebModule_1071087285887">
        <web>
          <web-uri>MyBankCMPWebv1.war</web-uri>
          <context-root>MyBankCMPWebv1</context-root>
        </web>
      </module>
      <module id="WebModule_1071091542238">
        <web>
          <web-uri>MyBankCMPWebCommon.war</web-uri>
          <context-root>MyBankCMPWebCommon</context-root>
        </web>
      </module>
      <module id="WebModule_1071092618175">
        <web>
          <web-uri>MyBankCMPWebv2.war</web-uri>
          <context-root>MyBankCMPWebv2</context-root>
        </web>
      </module>
    </application>



      JNDI 名称空间冲突



      EJB 本地接口是在共享的 JNDI 名称空间中注册的持久性对象引用。J2EE 应用程序的多个版本在一个单一的 WebSphere 域中使用共同的 WebSphere 名称空间。然而,分配给不同版本 EJB 本地接口的 JNDI 名称空间之间会产生冲突。为了避免这种冲突,可以直到部署阶段才将 EJB 本地接口对 JNDI 名称空间的引用绑定。对于 EJB 客户端(例如 Web 应用程序),可以通过 java:comp/env 来引用 EJB,而不是使用硬编码的 JNDI 命名。在 java:comp/env 环境中对 EJB 的引用是在部署描述符中声明的,并且在部署阶段将其绑定到适当的 EJB 本地接口引用版本。



      样本 2 中的代码展示了如何在应用程序的 java:comp/env 中使用 EJB 引用。图 5 展示了在部署阶段如何为 EJB 本地接口指定 JNDI 名,图 6 展示了在部署阶段如何将 java:comp/env 引用绑定到特定版本的 JNDI 名。



      样本 2. 使用 java:comp 来定位 EJB 本地接口      Context ctx = new InitialContext();
         accountHome = (AccountHome)javax.rmi.PortableRemoteObject.narrow(
           ctx.lookup("java:comp/env/ejb/Account"),AccountHome.class);

    图 5. 在部署阶段为 EJB 本地接口指定 JNDI 名

    同时托管 <a href=J2EE 应用程序的多个版本" />

    图 6. 在部署阶段将 java:comp/env 绑定到 JNDI 名

    同时托管 <a href=J2EE 应用程序的多个版本" />



      下表列出了在样本应用程序 MyBank 中使用的所有 JNDI 名:



      版本 Account EJB 组件 Transfer EJB 组件 最初版本 类名com.ibm.mybank.ejb.Account com.ibm.mybank.ejb.Transfer JNDI 名ejb/MyBank/Account ejb/MyBank/Transfer 版本 1 类名com.ibm.mybank.ejb.v1.Account com.ibm.mybank.ejb.Transfer JNDI 名ejb/MyBank/v1/Account ejb/MyBank/v1/Transfer 版本 2 类名com.ibm.mybank.ejb.v1.Account com.ibm.mybank.ejb.Transfer JNDI 名ejb/MyBank/v2/Account ejb/MyBank/v2/Transfer



      使用 WebSphere Application Server V5 的名称空间转储实用程序可以找到 WebSphere JNDI 名称空间的内容。



      外部资源冲突



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

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

    同时托管 <a href=J2EE 应用程序的多个版本" />



      应用程序设计问题



      将应用程序版本粒度降低到单个的 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 应用程序的多版本类结构图

    同时托管 <a href=J2EE 应用程序的多个版本" />



      为了确定 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 接口 package com.ibm.mybank.service;
    import javax.ejb.FinderException;
    import com.ibm.mybank.ejb.AccountLocal;
    public interface AccountServiceLocator {
      AccountLocal findAccountLocal(int accountId) throws FinderException;
    }



      样本 4. Transfer EJB 组件中用于得到 AccountServiceLocator 适当版本的代码 private AccountServiceLocator getAccountServiceLocator() 
    throws Exception {
      InitialContext initCtx = new InitialContext();
      String acctServLocClassName = (String)initCtx.lookup(
    "java:comp/env/AccountServiceLocatorClassName");
      return (AccountServiceLocator) Class.forName(
    acctServLocClassName).newInstance();
      }



      样本 5. 指定 ServiceLocator 适当版本的环境项 <session id="Transfer">
      <ejb-name>Transfer</ejb-name>
      <home>com.ibm.mybank.ejb.TransferHome</home>
      <remote>com.ibm.mybank.ejb.Transfer</remote>
      <local-home>com.ibm.mybank.ejb.TransferLocalHome</local-home>
      <local>com.ibm.mybank.ejb.TransferLocal</local>
      <ejb-class>com.ibm.mybank.ejb.TransferBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
      <env-entry>
        <description>
    The name of the AccountServiceLocator class
    </description>
        <env-entry-name>
    AccountServiceLocatorClassName
    </env-entry-name>
        <env-entry-type>java.lang.String</env-entry-type>
        <env-entry-value>
    com.ibm.mybank.service.v1.AccountServiceLocator
    </env-entry-value>
      </env-entry>
    </session>



      样本 6. Account ServiceLocator 接口的版本 1 的实现 package com.ibm.mybank.service.v1;
    import javax.ejb.FinderException;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import com.ibm.mybank.ejb.Account;
    import com.ibm.mybank.ejb.AccountLocal;
    import com.ibm.mybank.ejb.v1.AccountKey;
    import com.ibm.mybank.ejb.v1.AccountLocalHome;
    public class AccountServiceLocator implements
           com.ibm.mybank.service.AccountServiceLocator {
      private AccountLocalHome acctLocalHome = null;
      
      public AccountServiceLocator() throws Exception {
        acctLocalHome = getAccountHome();
      }
      
      public AccountLocal findAccountLocal(int accountId)
         throws FinderException {
        AccountKey key = new AccountKey(accountId);
        return acctLocalHome.findByPrimaryKey(key);
      }
      private AccountLocalHome getAccountHome() throws Exception {
        try {
          InitialContext initCtx = new InitialContext();
          Object objref = 
               initCtx.lookup("java:comp/env/bank/Account");
          return (AccountLocalHome) objref;
        } catch (NamingException ne) {
          ne.printStackTrace();
          throw new Exception(
            "Error looking up AccountHome object: " +
                ne.getMessage());
        }
      }
    }



      Java Product Versioning Specification中描述了如何开发向后兼容的可串行化值对象。目前,还没有用来开发向后兼容的 EJB stub 和 tie 的规范。因此,必须使用于每个版本的 J2EE 客户和服务器组件保持兼容。这可以通过使用适当的封装以及运行时检查很方便地得以实现,如下所述。



      封装选择



      通常有两种应用程序封装选择:



      将每个版本的 J2EE 应用程序中的所有 EJB 和 Web 模块封装成独立的自包含应用程序 EAR 文件,并且没有外部依赖性。 每个应用程序 EAR 文件可以单独地进行部署。在部署阶段,类加载冲突通过对不同的企业应用程序模块使用不同的应用程序类加载器得以解决。JNDI 名称空间冲突可以在应用程序装配阶段通过使用 <ejb-link> 描述符,并且使用无冲突的 JNDI 名而得以解决。servlet 路径冲突和外部资源冲突可以通过对 Web 应用程序的每个版本使用不同的上下文根而得以解决。尽管通过这种方式可以很简单地部署多个版本的应用程序,但是这种做法在组件模块改变时需要重新部署组件模块。图 9 显示了 EAR 文件的组成:一个 EJB JAR 文件( MyBankCMPEJB.jar )和一个 WAR 文件( MyBankCMPWeb.war )。这是一个自包含的应用程序,因为这个应用程序所需要的所有组件都包含在这个 .ear 文件中。所有其他的版本也要和这个 EAR 文件一样包含相同的文件。而且,多个版本的组件(例如 EJB Account)不能从公共的组件中访问,因为类名冲突在此情况中没有办法解决。因此,这种应用程序版本的粒度太大。

    图 9. MyBank 应用程序的自包含 EAR 文件的组成部分

    同时托管 <a href=J2EE 应用程序的多个版本" />



      在同一个应用程序中混合使用不同版本的 EJB 和 Web 组件模块。通过这种方式,单个的模块可以独立地进行升级。Web 模块应用程序可以在不需要改动 EJB 模块的情况下进行升级,并且可以在不影响现有版本的情况下添加新版本的 EJB 模块。为了做到这一点,您必须将应用程序分成多个 .ear 文件。在我们的样本应用程序中,图 9 显示的 EAR 文件被分成下面几个部分:每个版本的 EJB 有一个 .ear 文件(请参见图 10 和 11)、每个版本的 Web 应用程序的 .ear 文件包含多个版本的 .war 文件、每个版本的 Web 应用程序(请参见图 12)。



      图 10 给出了 Account EJB 版本 1 的 .ear 文件。注意其中有两个 JAR 文件:



      MyBankCMPEJBv1 包含 Account EJB 版本 1 的特定接口和实现。



      MyBankCMPEJBCommonUtility 包含版本 1 和版本 2 中共用的接口和实现,以及 Transfer EJB,并且 Transfer EJB 在从版本 1 到版本 2 的变化中不会发生任何变化。



      类似的,图 11 给出了 Account EJB 版本 2 的 .ear 的组成。相同的 .jar 文件 MyBankCMPEJBCommonUtility 被封装到 MyBankCMPEJB [v1v2] EAR 的两个版本中。



      图 12 给出了 Web .ear 文件的组成,以及 MyBankCMPWebv1.war 和 MyBankCMPWebv2.war (包括特定于版本的 CreateAccount servlet)和 MyBankCMPWebCommon (包括和版本无关的 TransferFunds servlet)。

    图 10. 应用程序 EAR EJB 版本 1 的组成

    同时托管 <a href=J2EE 应用程序的多个版本" />

    图 11. 应用程序 EAR EJB 版本 2 的组成

    同时托管 <a href=J2EE 应用程序的多个版本" />

    图 12. Web EAR 的组成

    同时托管 <a href=J2EE 应用程序的多个版本" />



      通过这种方法拆分应用程序可以使不同版本的 EJB 组件隔离开来,这种隔离是通过使用不同的应用程序类加载器实现的,一个 EAR 文件对应一个类加载器。不同版本的 Web 组件模块通过不同的 WAR 类加载器而隔离开来,一个 Web 模块对应一个 WAR 类加载器。由于 WAR 类加载器是应用程序类加载器的子类,所以所有版本的 Web 模块都可以封装在同一个企业应用程序中。为了使应用程序类加载器或 Web 类加载器可以加载通用的实用程序类 JAR 文件,就必须在类路径头部指定这些 JAR 文件依赖于 EJB JAR 或 WAR 清单文件(manifest file)。样本 7 给出了 MyBankCMPEJBv1.jar 中的清单文件的例子;样本 8 给出了 MyBankCMPWebv1.war 中的清单文件的例子。



      样本 7. MyBankCMPEJBv1.jar 中用于指定相关 JAR 文件的清单文件 Manifest-Version: 1.0
    Class-Path: MyBankCMPEJBCommonUtility.jar



      样本 8. MyBankCMPWebv1.war 中用于指定相关 JAR 文件的清单文件 Manifest-Version: 1.0
    Class-Path: MyBankCMPEJBCommonUtility.jar MyBankCMPEJBv1Utility.jar My
    BankCMPWebCommonUtil.jar

    图 13. 在 WebSphere 应用程序服务器 V5 中配置共享类库

    同时托管 <a href=J2EE 应用程序的多个版本" />



      为了使 Web 应用程序能够调用 EJB JAR 组件,需要将 Web 应用程序中使用的 java:comp/env EJB 引用绑定到适当版本的 EJB 本地接口的 JNDI 名。如果 Web 应用程序组件和 EJB 组件通过不同的类加载器来加载,那么 EJB 组件的本地接口是不能使用的。您必须使 EJB 远程接口的客户端类对应用程序类加载器可用。这可以通过两种方式来得以实现:



      EJB 远程接口客户端类可以作为实用程序 JAR 文件封装到 Web EAR 文件中,并且可以通过修改用于每个 Web 应用程序 WAR 文件的清单文件,在类路径头部下面添加 EJB 客户端类实用程序 JAR 文件。(请参见样本 8)。



      将 EJB JAR 作为共享的类库添加到 Web WAR EAR 应用程序中。



      跟踪分布式组件的变化



      当将多个版本的 J2EE 应用程序组件部署到同一个 WebSphere 域中时,为了安全起见,应该进行一些运行时检查。另外,保留对部署模块版本变化的跟踪记录也是一种可行的做法。 Java Product Versioning Specification 将 Java 包定义为一个可以开发、封装、验证、升级及分布式的一致性单元。JAR 文件的清单文件(manifest file)包含指定包版本的属性。样本 9 给出了在清单文件中指定 J2EE 应用程序组件版本的例子。在运行时,J2EE 组件(包含在一个包中)的版本可以从 java.lang.Package 接口得到。样本 10 给出了获得在清单文件中指定的版本代码的例子。包的版本信息可以在运行时用来检查分布式应用程序组件的兼容性。例如,根据 Java 版本规范可以从 EJB JAR 清单文件中取得 EJB 的版本信息。客户端的版本信息可以通过客户端 JAR 文件获得,服务器端版本信息可以从服务器端 JAR 文件获得,并且通过 EJB 组件的远程接口来查询,从而可以在运行时检查版本的兼容性。



      样本 9. 在 EJB JAR 清单文件中指定的包版本信息 Manifest-Version: 1.0
    Name: com/ibm/mybank/ejb/v2/
    Specification-Title: "EJB"
    Specification-Vendor: "Sun Microsystems, Inc.".
    Specification-Version: "2.0"
    Implementation-Title: "MyBank EJBs"
    Implementation-Vendor: "IBM"
    Implementation-Version: "1.4"



      样本 10. 从 JAR 清单文件中获得包版本的应用程序代码   public String getImplVersion() {
        Package p = this.getClass().getPackage();
        String specTitle = p.getSpecificationTitle();
        String specVersion = p.getSpecificationVersion();
        String implTitle = p.getImplementationTitle();
        String implVersion = p.getImplementationVersion();
        String messageLine = "Server Specification: " + specTitle +
                     ", specification version: " +
                     specVersion +
                   ", Implementation: " + implTitle + 
                     ", implementation version: " +
                     implVersion + "n";    
        return messageLine;
      }



      会话对象的不兼容性



      Java 产品版本控制规范(Java Product Versioning Specification)讨论了可以对可串行化类做出那些改变,或者不可以作出那些改变,以保持其和以前版本的兼容性。对会话对象类的改变也是要遵循相同的法则。另外,自定义的 readObject 和 writeObject 方法可以加到该类的各个版本中。



      结束语



      最近,应用程序的版本问题引起了广泛的注意。对待版本问题的传统做法往往是轻率地使用“大手笔”的方法使应用程序从一个版本转变到另外一个版本,然而,也可以在较小粒度的水平上进行增量升级,获得使 J2EE 组件的不同版本得以共存。本文提出了在满足同时托管 J2EE 组件的多个版本的要求时所面临的挑战,并且提供了一些方法(既考虑到了部署阶段又考虑到了设计阶段)来帮助您解决这些问题。


    (学 习 吧:www.xuexibar.cn)

    此文章为学 习 整理或来自网络,内容仅供访问者参考,版权归原作者所有,转载请注明出处!