将 WebLogic 启动代码迁移到 WebSphere Application Server V5

将应用程序从竞争平台迁移到 WebSphere Application Server 需要考虑许多问题,其中之一就是如何将专有 API 的使用迁移到 J2EE 标准。本文研究了几种将特定的 WebLogic Server API 的使用迁移到 WebSphere Application Server 的可选方案。


将应用程序从竞争平台迁移到 WebSphere Application Server 需要考虑许多问题,其中之一就是如何将专有 API 的使用迁移到 J2EE 标准。本文研究了几种将特定的 WebLogic Server API 的使用迁移到 WebSphere Application Server 的可选方案。由于J2EE 中没有与专有 API 直接相对应的构件,所以人们开始考虑一些替代方案,其中每种都有不同的折衷选择。

引言


最近,在把为 BEA WebLogic Server编写的应用程序迁移到 WebSphere Application Server时,我们遇到了一组专有 API-- T3StartupDefT3ShutdownDef --的使用,它们允许应用程序注册服务器在启动和关闭时所执行的代码。本文研究了几种将WebLogic Server 的 T3StartupDefT3ShutdownDef API 的使用迁移到 WebSphere Application Server的可选方案,并讨论了各种方案的优缺点。

背景


weblogic.common.T3StartupDef 接口包含在 WebLogic Server中。服务器在启动时利用这些接口的实现来执行自定义代码。 T3StartupDef s 连接到应用服务器实例本身,而不是任何特殊的J2EE 应用程序或模块。

T3StartupDef 接口提供两个方法:

  • setServices(T3ServicesDef 服务)
    由服务器在任何其他方法之前调用,使实例能够初始化 T3Services 变量,以提供对 WebLogic 服务器中的各种服务的访问。
  • 启动(字符串,可散列)
    由 WebLogic Server 在所有 J2EE 模块载入、初始化并启动之后,并在任何传入请求被接受之前调用。

清单1 展示了一个简单的 T3StartupDef 实现,为了示范的目的,假设创建并初始化了一个虚构的规则引擎。这个类用来初始化一个规则服务器,并在服务启动时预获取一些规则。

清单 1.T3StartupDef 的示例



package com.issw.startup;
import weblogic.common.*;
import javax.naming.*;
import com.issw.rules.*;

public class MyT3StartupImpl implements T3StartupDef {
  private T3ServicesDef services;
  public void setServices(T3ServicesDef services) {
    this.services = services;
  }

  public String startup(String name, Hashtable args)
  throws Exception {
    RulesEngine rs = new RulesEngine();
    rs.start();
    rs.preFetchRules();

    //now we will stash it in JNDI for future use
    InitialContext ic = new InitialContext();
    InitialContext ic = new InitialContext();
    Context startupContext = ic.createSubcontext("startup");
    startupContext.bind("ruleserver", rs);
  }
}

                

T3ShutdownDef 与其非常相似,惟一的不同之处在于它的关闭(字符串,可散列)方法是在服务器关闭时调用的,调用的时间在最后的传入请求处理完之后,而在 J2EE 模块关闭之前。

T3StartupDef s 和 T3ShutdownDef s必须通过要运行的服务期进行显式注册。启动类实现了该接口,并在 WebLogic Server 的 config.xml 文件中进行了注册。

T3StartupDefT3ShutdownDef 接口的文档请参见 参考资料

Web 容器的启动代码
Servlet 规范提供了两种明确定义的机制来运行 Web 容器中的启动代码:

  • 通过 init() 方法初始化 Servlet;
  • 初始化 Servlet 上下文。

下面将讨论这些选项。

Servlet 的初始化


Servlet 是在载入时初始化的;在初始化的过程中,Servlet 的 init() 方法被调用(如果它存在的话)。在默认情况下,Servlet 在它们首次被访问时载入并初始化。另外,servlet 规范规定单独的 Servlet 在 Web 容器启动时载入。总而言之,在 Web 容器启动时载入 Servlet 的设置,并且通过 init() 方法提供给 Servlet,这种组合可以用于提供启动代码。

Servlet 的 init() 方法(如果它存在的话)是在 Servlet 载入之后且在它响应任何请求之前直接被调用的。destroy() 方法(如果它存在的话)是在包含的 Web 模块关闭时且在最后的请求完成之后被调用的。考虑到这一生命周期,这些方法的组合在某些情况下对应用程序启动和关闭代码可能是合适的。

您可以创建一个 Servlet(例如 StartupServlet )作为您调用的 Web 应用程序的一部分,并将所有启动逻辑放在它的init() 方法中。在默认情况下,Servlet 在它们首次被访问时载入并初始化。Servlet 规范通过用载入顺序将它们标记为“load at startup”来规定预载入 Servlet。在默认情况下,Servlet 从最小的数字开始载入并初始化(载入顺序为“0”的 Servlet 是在所有其他的 Servlet 载入之前载入的;负值保证 Servlet 将被载入,但是不保证顺序)。

启动 Servlet 实际上并不需要响应任何的用户请求。如果您愿意,您可以阻止 Servlet 响应用户请求,方式是不为 Servlet 提供任何 Servlet 映射条目,并且禁用 Servlet 的服务(HttpServletRequest、HttpServletResponse)方法(使该方法抛出 ServletException)。

图 1 显示在 WebSphere Studio Application Developer5.1 中设置必要的部署描述符参数的位置。

图 1. 配置 servlet 以在启动时载入
配置 servlet 以在启动时载入

Servlet 的 init() 方法总是在应用程序服务器和 Web 容器启动之后被调用的。因此,init()方法可以访问应用服务器资源,如数据源和队列。

清单 2 展示了启动 Servlet 的一个示例,它初始化了我们虚构的规则引擎。

清单2. Servlet init() 方法的示例



package com.issw.servlets.startup;

import java.io.*;
import javax.naming.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.issw.rules.*;

public class StartupServlet extends HttpServlet {
  public void init() throws ServletException {
    RulesEngine rs = new RulesEngine();
    rs.start();
    rs.preFetchRules();

    //now we will stash it in JNDI for future use
    InitialContext ic = new InitialContext();
    Context startupContext = ic.createSubcontext("startup");
    startupContext.bind("ruleserver", rs);

  }

  protected void service(
    HttpServletRequest request,
    HttpServletResponse response)
  throws ServletException, IOException {
    throw new ServletException("Do not use.");
  }
}

                

这种方法具有几个优点,其中包括:

  • 这种方法利用标准的机制来进行配置(通过使用 Servlet参数),这是 Servlet 和 J2EE 规范所完全支持的。
  • 该 Servlet 的 init()方法是在应用程序服务器本身启动和配置之后才运行的,这意味着可以访问应用程序服务器资源(比如数据源和队列)。
  • 该 Servlet 的 destroy() 方法是在 Web 容器完成最后请求之后且在容器和应用程序服务器实例关闭之前运行的。
  • 在任何其他的 Servlet 载入和初始化之前,可以配置该 Servlet(通过使用载入顺序)并进行运行。
  • 它可以运行在 WebSphere Application Server V4.0 和 V5.0(以及其他与 J2EE 1.2+ 兼容的应用程序服务器)上。

也可以在 Servlet 规范中发现这种方法一个潜在的缺点。根据规范,当“ Se rvlet 容器确定应该从服务中除去 一个 Servlet 时”(Servlet 2.3,第 2.3.4 节),这个 Servlet 就被销毁。事实上,Web 容器有可能销毁一个 Servlet,然后在另一时间重新载入并重新初始化这个 Servlet(如果需要的话)。这样做会导致置于 init() 方法中的启动代码有可能在应用程序的生命周期中多次运行(虽然我们还没有在 WebSphere Application Server 中观察到这种行为)。

初始化 Servlet 上下文

javax.servlet.ServletContextListener 接口是在 Servlet2.3 API中引入的。这个类的实例(它显式注册在Web 模块的部署描述符中)响应生命周期事件的集合,这种集合是由于 Web 容器中状态的改变触发的。

contextInitialized(ServletContextEvent)方法是在 Web 模块准备好处理请求时由 Web 容器调用的。也就是说,该方法是在应用程序服务器配置好它所有的资源、Web 容器已经启动并且所有的 Servlet 已经预载入(如果需要的话)之后被调用的。这是预获取和缓存所需的值或设置以后需要的对象的一个好地方。contextDestroyed(ServletContextEvent)方法的作用是通知 Web 模块将要关闭。

要在 WebSphere Studio Application Developer 中创建一个 ServletContextListener (您必须在 J2EE 1.3/Servlet 2.3 项目下这样做),可以在您的 Web 应用程序项目中右键单击 Java Source,然后选择 New => Life-cycle Listener。使用 New Life-cycle Listener 向导(图 2 所示)来创建 javax.servlet.ServletContextListener 类的实例。确保选项 add thelistener to the deployment descriptor ( web.xml ) 被选中。

图 2. 用 WebSphere Studio Application Developer 5.0 创建一个新的ServletContextListener
用 WebSphere Studio Application Developer 5.0 创建一个新的 ServletContextListener

要注册这个 ServletContextListener, 可以将 listenerType元素添加到 Web项目的部署描述符中。这个附加元素将会出现在您的 Web项目的部署描述符的 Listeners选项卡中,如图 3 所示。

图 3. 通过 Web 应用程序注册侦听者
通过 Web 应用程序注册侦听者

清单3 展示了 ServletContextListener 的一个样本实现。

清单 3. ServletContextListener 的样本实现



package com.issw.servlets.startup;

import javax.servlet.*;
import com.issw.rules.*;

public class StartupServletContextListener implements
ServletContextListener {
  public void contextDestroyed(ServletContextEvent event) {
  }

  public void contextInitialized(ServletContextEvent event) {
    RuleServer rs = new RuleServer();
    rs.start();
    rs.preFetchRules();

    //now we will stash it in JNDI for future use
    InitialContext ic = new InitialContext();
    Context startupContext = ic.createSubContext("startup");
    startupContext.bind("ruleserver", rs);
  }
}

                

这个选项本质上同 Servlet 的 init()方法是一样的,它们之间的主要主要区别有以下点点:

  1. 该行为是在 Web 容器启动之后被调用的,因此所有配置好了的 Servlet 都已经载入并初始化了。
  2. 它只在支持J2EE 1.3 或更新版本的应用程序服务器上有效(在 WebSphere Application Server V4 上无效)。

EJB 容器的启动代码

EJB 2.0 规范没有为代码在启动时运行提供条件。实际上,EJB规范通过禁止静态初始化器的使用和其他可能用于这种目的的静态行为来严格限制可用的选项。

然而,至少还有两个选项:

  • 可以利用 Web 容器启动代码来调用 EJB 中的启动行为。
  • WebSphere Application Server Enterprise 中的启动 Bean 提供了启动行为。

下面将讨论这些选项:

从 Web容器启动

利用前面所讨论的方法,可以通过 EJB使用 Web 容器来执行启动操作。例如,应用程序可能使用无状态会话Bean来从数据库里收集配置信息,或者从性能方面考虑,使用有状态会话Bean来预载入实体 Bean。

清单 4 展示了一个 Servlet 的 init()方法调用 EJB 中的方法来执行一些启动代码的示例。

清单 4. 使用 Servlet 来调用 EJB 启动代码的示例



package com.wtb.servlets;

import javax.ejb.*;
import javax.naming.*;
import javax.servlet.*;
import com.issw.beans.*;

public class StartupServletContextListener
implements ServletContextListener {
  private static final String prefetchHome =
    "java:comp/env/PrefetchHome";

  public void contextDestroyed(ServletContextEvent event) {
  }

  public void contextInitialized(ServletContextEvent event) {
    InitialContext context = null;
    try {
      context = new InitialContext();
      PrefetchLocalHome home =
        (PrefetchLocalHome)context.lookup(prefetchHome);
      PrefetchLocal bean = home.create();
      bean.doPrefetch();
    } catch (NamingException e) {
      // TODO* Add logging.
      // For now, we'll let life go on.
    } catch (CreateException e) {
      // TODO Add logging.
      // For now, we'll let life go on.
    } finally {
      try {
        context.close();
      } catch (NamingException e) {
      }
    }
  }
}
                
* 在 WebSphere Studio V5.1 中,当您在 Java 注释的开头包括“TODO”时,它会作为一个任务出现在任务列表中。请用它来标记您需要再次访问的代码。

上面的示例会使提供 EJB的启动代码显得很简单。然而,有一个问题是:J2EE规范并没有提供标准的机制来为企业档案文件中的的模块执行特定的载入顺序。企业档案文件的部署描述符中指定的模块的顺序并不一定会影响WebSphere Application Server V5 中载入顺序。

然而,WebSphereApplication Server V5提供了一种机制来执行特定的模块载入顺序。WebSphere ApplicationServer 的配置可以更改为以一种基于权重的特定顺序来载入 J2EE模块。这种机制不利的一面是是它与代码是完全分离的:相关的配置并不是 EAR文件的一部分,并且在应用程序每次部署到服务器时都需要单独更新。

在 WebSphere Studio中,要改变企业应用程序中模块的顺序,可以打开您的测试服务器中的服务器配置编辑器,选择 Applications选项卡,如图 4所示。在这里,您可以选择您的应用程序,编辑它的启动权重,该启动权重会影响到它相对于其他企业应用程序载入的顺序。在本例中,最有效的是展开企业应用程序,为每个单独的Web 和 EJB模块设置启动权重,这样,只要必要,就可以解析依赖性。具有最小数值的启动权重的模块优先权最高。

图 4. 在 WebSphere Studio Server Configuration Editor 中设置 Web 和 EJB 模块的相对启动权重
在 WebSphere Studio Server Configuration Editor 中设置 Web 和 EJB 模块的相对启动权重

启动权重依赖于应用程序服务器实例(或单元)配置,而不是应用程序。这意味着当您将应用程序从 WebSphere Studio部署到 WebSphereApplication Server时,必须再次提供这些启动权重。实际上,这些值在每次应部署或重新部署应用程序时都必须指定。

要在WebSphere Application Server管理控制台中配置权重值,可以导航到每个模块的配置,然后设置这些值,如图5 所示。

图 5. 在 WebSphere Application Server 管理控制台中设置每个 EJB 模块的相对启动权重
在 WebSphere Application Server 管理控制台中设置每个 EJB 模块的相对启动权重

设一旦置好所需的启动权重,就保存您的更改并重新启动应用程序。当您每次重新部署应用程序时,都需要重新访问权重的设置。脚本可以使这一过程更安全地重复(这样的脚本的构造超出了本文的讨论范围)。

遵守J2EE 规范的 EJB 启动


上面的方法是可行的,并且实现起来相对简单。然而,这种解决方案不是标准的,为了确保正确地部署,它给管理人员增加了压力。

在 J2EE的框架内提供类似的功能是可能的。然而,这种解决方案却显得繁琐,而且依赖于应用程序服务器的附加配置(虽然都是完全标准的配置)。

不是直接调用EJB,servlet 可以改为通过 JMS 调用消息驱动 Bean。如果 EJB容器在Web 容器之前载入并初始化,那么消息驱动 Bean 就会立即选取和处理初始化方法。如果 Web容器首先启动,启动代码就会把消息放在它所在的队列上,并且等待 EJB容器载入和消息驱动 Bean 使用该消息。

当消息驱动 Bean 接收到启动消息时,它可以处理启动代码本身。对于调用EJB 容器中包含的启动代码,这应该很有效,但是如果 EJB容器需要把值作为启动处理的一部分传回给 Web 容器,这种方法就无效了。

如果用 JMS来提供启动代码,J2EE 模块的载入顺序就不相关。这种解决方案虽然相当繁琐和复杂,但是却遵守 J2EE规范,并且应该在所有的 J2EE 1.3+应用服务器上都是有效的。

WebSphere 启动 Bean

WebSphere Application Server Enterprise Version 5.0包含一个非常强大的编程模型扩展,称为启动 Bean(请参见 WebSphere 应用服务器企业信息中心,以获得完整的描述)。启动Bean 是一种特殊的会话 Bean,它们只是在应用启动之前由 EJB容器载入和执行的。与我们早期研究的某些面向 Web 应用应用程序的解决方案不同,当启动Bean 的启动方法被调用时,所有的 J2EE模块(包括 Web 和 EJB 模块)都已经被载入,并且该 Bean 可以充分利用EJB 和所有其他 J2EE特征,而不必担心时间安排的依赖关系。另外,在启动 Bean成功完成执行之前,应用程序不会完成它的启动。

启动Beans 直接依赖于特定的企业应用程序:它们一起定义在 EJB模块内通常的 EJB 中。在企业应用程序及其所有的模块被载入和初始化之后且在应程序用接受任何传入请求之前,每个启动Bean的远程接口的 start()方法都被调用来完成应用程序启动时需要做的任何事情;同时还有一个 stop()方法,它是在企业应用程序停止之前调用的。

为了让应用将Bean 标记为启动 Bean,应用程序开发人员必须如下约束条件:

  • 该 Bean 必须是带有远程接口的 EJB 2.0 会话 Bean。该 Bean 的本地接口必须为 com.ibm.websphere.startupservice.AppStartUpHome (这个接口是在 WebSphere Application ServerEnterprise 所包含的 startupbean.jar 中定义的)。
  • 该 Bean 的远程接口必须是 com.ibm.websphere.startupservice.AppStartUp 或其扩展。
  • AppStartUp 接口中的 start() 和 stop() 方法可以包含任何事务属性(TX_MANDATORY 除外)。这是为了保证该 Bean 创建它自己的事务(如果需要的话)。

Bean可以是有状态,也可以是无状态的。如果是有状态的,则相同的实例可用作 start() 和 stop()。EJB的环境变量 java:comp/env/wasStartupPriority (整数值)可以用来设定启动Bean被调用的顺序。这使得可以指定排序(如果在一个模块中存在多个启动Bean 的话)。Bean 以与启动时相反的顺序停止。这保证了启动Bean 之间排序关系。

启动 Bean可以访问部署在应用程序服务器上的所有资源(如数据源、队列等等)、以及部署在企业应用程序中的所有 J2EE资源。例如,启动 Bean 可以用于与异步 Bean 连接来做诸如准备实体Bean 缓存、设置工作线程或预定任务的事情。(异步 Bean是由应用程序服务器进行有效线程管理的,它用于提供 J2EE因缺少对应用程序线程的支持而拒绝的服务。要了解更多的信息,请参考 WebSphere 应用服务企业信息中心。)

您可以通过 WebSphere Studio Application DeveloperIntegration Edition(以下简称为 Application Developer)来创建启动Bean。在您可以将启动 Bean 添加到 EJB 项目之前,需要先把必要的库文件 startupbean.jar 添加到项目的构建路径中(这个库包含在运行时支持启动Bean 所需的代码)。具体步骤就是,打开 EJB项目的属性对话框,选择 Java Build Path页。在 Libraries选项卡上,选择 Add Variable。在出现的对话框“New Variable Classpath Entry”对话框中,选择 WAS_EE_V5变量,然后选择 Extend。在“Variable Extension”对话框中,展开 lib目录,然后选择 startupbean.jar。单击 OK。结果如图 6 所示。

您需要做的只是使创建过程在 Application Developer中成功地完成;对于 WebSphereApplication Server Enterprise,这个库会自动包含进运行时类路径中。

图 6. 将 startupbean.jar 添加到构建路径
将 startupbean.jar 添加到构建路径

之后,以常规方式使用 WebSphere Studio向导创建 EJB 会话 Bean。这个 Bean 可以是有状态的,也可以是无状态的。设置下列值,如图7 所示:

  • Remote home interface: com.ibm.websphere.startupservice.AppStartUpHome
  • 远程接口: com.ibm.websphere.startupservice.AppStartUp .

图 7. 创建启动 Bean
创建启动 Bean

这时,您会看到任务窗格中有两个警告,指示您还没有实现在EJB 远程接口中定义的 start() 和 stop()方法。您需要使用业务逻辑来提供这些方法。基本样本如清单5 所示。



package com.ibm.test;
public class StartupSampleBean implements javax.ejb.SessionBean {
  private javax.ejb.SessionContext mySessionCtx;

  public boolean start() {
    System.out.println ("In Application Start");
    Cache ch = new Cache();
    ch.preFetch();
    return true;
  }

  public void stop() {
    System.out.println ("In Application Stop");
    //do cleanup here as needed
  }

  // The standard EJB methods, including getSessionContext(),
  // setSessionContext(), ejbCreate(), ejbActivate(), etc.
  // are not shown.
}
                

启动 Bean 具有许多的优点:

  • 没有时间安排依赖性问题:在 EJB 模块启动以后您就不必再“检测和查看”了;启动 Bean 是服务器启动前最后需要做的最后的事情。
  • 具有拒绝能力:如果启动 Bean 因为某些灾难出现而返回 False,则应用程序启动将不会继续进行下去。
  • 您可以有多个启动 Bean,并且能够很容易地确定它们启动顺序的优先权。
  • 启动 Bean 可以使用完整的 IBM WebSphere Application Server J2EE 和企业扩展编程模型(它们的功能很强大,例如,与异步 Bean 连接)
  • 启动 Bean 运行在完全的 J2EE 安全性上下文中。

WebSphere Application Server Enterprise是使用启动 Bean 所需的。

结束语

寻找专有技术的合适替代技术代码迁移最具挑战性的方面之一。一旦可能,我们就应当选择符合标准或支持配置的解决方案。

J2EE 规范当前并不包括对 startup 或 shutdown的任何特定支持。正如本文所讨论的,servlet 的 init() 和 destroy()方法可以用于这种目的。如同 ServletContextListener的实现一样。不幸的是,只有在保证包含的 Web模块本身载入后,startup代码才会运行。并没有标准的方法来保证譬如 EJB模块载入后 startup 代码会运行,但您可以在 WebSphereApplication Server 里使用模块启动权重来使 startup 代码运行。

虽然 startup bean 目前并不是任何标准的一部分,但它们得到了完全的支持。而且它们是很优秀而且相对简单的解决方案;startupbean 本身是与它们一起运行的应用程序同时配置和管理的。startupbean可能是最通用的解决方案,它们不仅支持访问所有应用程序服务器资源,而且还支持访问企业应用程序中的每个模块的内容。

致谢

感谢 Roland Barcia 耐心地帮助我们。

参考资料

T3StartupDef 接口文档
T3ShutdownDef 接口文档

作者简介
Wayne Beaton是 IBM 软件集团的 WebSphere 软件服务部的一名高级软件顾问。他的研究重点是将 WebSphere 的早期版本和竞争产品迁移到 WebSphere Application Server 5.0。他与别人合著过两本关于迁移主题的 IBM 红皮书。Wayne 的多种角色使他参与了许多其他有趣的工作,包括 WebSphere Skills Transfer 编程和通常的咨询。Wayne 喜欢利用他的余暇时间说服人们相信极限编程、重构和单元测试实际上是很有效的。您可以通过 wbeaton@ca.ibm.com与他联系。


Sree Anand Ratnasinghe 是IBM WebSphere 软件服务部的顾问软件工程师。Sree 是 WebSphere Application Server 早期版本的开发人员。她现在是 WebSphere Enablement Consulting Team 的成员,她实现概念证明 (proofs-of-concept), 为对 WebSphere Application Server 及 WebSphere 家庭产品感兴趣的顾客提供指导。Sree 拥有卡内基梅隆大学(Carnegie Mellon University)信息网络的硕士学位。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值