技术开发 频道

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

  【IT168 技术文章】

         引言

  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 应用程序之间的冲突。外,除了以上的各种冲突,也要考虑下面的应用程序设计和开发问题:

  应用程序设计问题

  为了能够支持多个版本的分布式 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

  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 应用程序的类结构图

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

0
相关文章