IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  WebSphere  >

移植到 IBM WebSphere Application Server,第 I 部分: 设计利于移植的软件

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Wayne Beaton (wbeaton@ca.ibm.com), AIM 服务

2001 年 12 月 01 日

软件移植是把用一种语言或平台开发的应用拿来并移动到另一种语言或平台的过程。在企业 Java 应用开发领域中,软件移植是在两种互为竞争的应用服务器之间或同一台应用服务器的不同版本之间移动一个应用的过程。Java 2 企业版(Java 2 Enterprise Edition (J2EE))所规定的标准为消除部署平台之间的微妙差异做了大量工作,使移植比以前大大地容易了。理想境界,J2EE 应用不用修改就可以容易地从一台应用服务器移动到下一台应用服务器。移植是一个庞大的主题。本文是分为 3 个部分的文章的第 1 部分.

软件移植是把用一种语言或平台开发的应用拿来并移动到另一种语言或平台的过程。在企业 Java™ 应用开发领域中,软件移植是在两种互为竞争的应用服务器之间或同一台应用服务器的不同版本之间移动一个应用的过程。Java 2 企业版(Java 2 Enterprise Edition (J2EE))所规定的标准为消除部署平台之间的微妙差异做了大量工作,使移植比以前大大地容易了。理想境界,J2EE 应用不用修改就可以容易地从一台应用服务器移动到下一台应用服务器。

移植是一个庞大的主题。本文是分为 3 个部分的文章的第 1 部分,设计利于移植的软件,讨论一些软件开发最佳实践方法,这些惯例可以降低代码移植的开支。 第 2 部分给出一个完成移植的总体计划。第 3 部分讨论之所以进行移植的几个常见原因,并提供一些使移植过程尽可能容易的建议。

J2EE,标准和专有解决方案

标准(例如 J2EE 和 Java 语言本身)的发展在使移植成为一项相对容易的任务方面已经做了很多工作。这是标准的主要好处之一。但标准的发展是缓慢进行的。与此同时,飞速增长的大量问题需要大量的解决方案。例如,在早期的服务器端 Java 方面,为了解决一个最终由 JavaServer Pages™(JSP)标准解决了的问题,人们创建了由大量不同的 HTML/Java 混合而成的解决方案。

J2EE 将大量的规范打包在一起,为服务器端应用开发提供大量服务。遵循 J2EE 的服务器几乎提供了构建一个企业应用所需的一切。尽管 J2EE 很全面,然而,它并不涵盖您可能曾经关心的所有事情。在快速发展的 Java 开发领域,任何一套标准要涵盖一切都是不可能的。需求变化太快。例如,标准目前并不涵盖门户网站和个性化,但它们却是很多应用的重要部分。人们开发专有解决方案来填充标准中存在的空隙。有时,这些专有解决方案发展成标准;更多的时候,人们开发补充标准。

基于提供的某些专有服务之上,您可能已经选择了应用服务器、数据库、中间件或任何东西。您可能正在移动到 IBM WebSphere® 以利用它的一些专有服务。这些专有服务通常就是将竞争产品彼此区别开来的东西。专有解决方案并不是有天生的不足之处,但却让您依赖于特定的供应商,使您将来的移植和变动依赖于该供应商对那些解决方案的继续支持。

即使是标准涵盖了的服务也不是绝对可靠的。作为 J2EE 的一部分,Java 数据库连接(Java Database Connectivity(JDBC))为从 Java 访问关系数据库提供了一种标准机制。JDBC 鼓励创建标准的 Java 代码,但几乎不阻止您利用特定的数据库扩展。只在如果您小心地使用 JDBC “标准”以避开专有扩展,将自己从实际数据库的细节中隔离出来时,JDBC “标准”才有作用。

J2EE 还是相当新的事物。事实上,完全支持 J2EE 的应用服务器为数还较少。已部署的充分地使用了 J2EE 的应用的数量还更少(相对地说)。当今实际应用的大量应用程序都在第一代应用服务器上运行,那时,标准还没有形成。随着这些第一代应用服务器的发展,它们不得不补充大量服务。

ATG Dynamo 就是一个第一代应用服务器,它用 JHTML、Droplet、FormHandler 和一个专有的数据库访问机制来实现服务器端应用。虽然这个应用服务器已被升级以支持一些新近的标准,但在它上面构建的很多应用却并未跟上这些标准的步伐。如果您打算从 ATG Dynamo 移植到 IBM WebSphere Application Server,那么一切都不会丢失;您的很多、也许是多数代码可能不用修改就可以移植。全部业务模型类用不着一点改动就可以移植。有些 servlet 可能只需少量改动。至少,单个方法和代码块有可能是可重用的。尽管如此,即使大量代码只需少量修改就可以移植,移植工作仍然有很多事情要做。

您需要有一个既利用了专有服务,同时又可降低将来变动解决方案时的成本的策略。设计时考虑变动是这种策略的一个关键要素。

为移植做准备

那么如何为移植做准备呢?答案的基石是经典的软件开发最佳实践方法,这些最佳实践方法超越了 Java 甚至面向对象编程。软件开发团体知道这一答案已经很久了。它的本质归结为:降低应用内部的耦合。耦合是衡量应用代码的每个部分对其它部分了解程度的尺度。通过将耦合保持在最低限度,将代码块和变动工作隔离开来。以下最佳实践方法将帮助您降低变动成本:

  • 尽可能符合标准(在本案例中是 J2EE)。
  • 避开专有扩展。如果无法避开专有扩展,则对它们的使用进行抽象,为的是把代码块和将来的变动隔离开来。
  • 应用采用分层结构。
  • 编写并维护自动测试(这些测试对开发和移植有帮助)。

最佳实践方法:符合标准

因为要从这么多标准中作出选择,所以标准问题是棘手的。标准并不象支持它们的团体那么可靠。从各个方面看,J2EE 都是值得信赖的好标准。然而,小心地对待标准的使用是重要的。正如以前提到过的,即使是象 JDBC 这样的标准也会让您陷入麻烦。

比只是符合标准更重要的也许是采用一种使自己能跟上标准的发展步伐的理念。标准常常要经过多个版本,这使符合标准更加困难。随着 J2EE 的发展,新的功能被添加进去,而一些老的功能则被退出。退出周期通常过于冗长,这使应用开发者要花很长时间来跟上它。按 Java 的说法,因故弃用的类型和方法在 javadoc 中标记为“deprecated”。开发工具,例如 VisualAge® for Java,通过识别 deprecated 代码,有助于您跟上标准。

将符合标准作为开发计划中的高优先级,不是只针对最初的开发,而是作为一个长期的开发计划。当标准更新时,在您的开发计划中加入一些工作时间,用于更新代码,保证代码符合那些新标准。

最佳实践方法:抽象,抽象,再抽象

抽象是一种给功能性加上单个一致的表面的方式。不是让应用的每个部分直接使用一些技术,而是让这些部分通过一个抽象层来访问那些技术。如果后来您要对存在问题的技术进行修改,则您只须修改抽象下面的代码,所有使用这些抽象的东西应正常工作。

在最近的一个移植到 IBM WebSphere Application Server 的工作中,工作总量由从 Oracle 数据库转变到 DB2® 所需的工作混合构成。而且,竞争的应用服务器不支持使用数据源的 JDBC 连接池,所以开发者们构建他们自己的连接池解决方案。不幸的是,他们的连接池解决方案也不一致。

早期的 JDBC 用 DriverManager 类获取数据库的连接。DriverManager 的目的原来是供 applet 和独立应用程序使用,这些应用程序通常只需要一个连接。对于服务器端 Java,事情发生了变化。当多个线程并发地“撞击”一台服务器时,可能会需要几百个或几千个连接。

数据库连接是昂贵的资源,它的构建和维护花费很大。人们独立进行了大量的努力,想提供一种连接池机制,这种机制允许多个线程共享数量相对较少的连接。例如,IBM WebSphere Application Server(版本 2.0)就有一个连接池的专有机制。数据源是新近添加到 JDBC 规范中的,它提供连接池。

应用程序的代码中至少撒布有 80 个对 DriverManager 的直接引用。应用程序中反复出现类似于如下所示的代码片段:


...
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
String database = DbParameters.getDbName();
String user = DbParameters.getDbUserName();
String password = DbParameters.getDbPassword();
Connection connection =
    DriverManager.getConnection ("jdbc:oracle:oci8:@"+database, user, password);
PreparedStatement statement =
    connection.prepareStatement("select balance from account where id = ?");
statement.setInt(1, accountId);
...
    

随着数据库驱动程序的修改,必须对全部 80 多个引用作修改。事实上,这些代码显现了甚至更大的问题:定制连接池是可用的了,但这些代码尚未更新以使用该定制连接池。换个角度,如果在代码的结构有点象以下所示时,请想象一下对数据库驱动程序进行修改所产生的影响:


...
        
        Connection connection = getConnection();
PreparedStatement statement =
    connection.prepareStatement("select balance from account where id = ?");
statement.setInt(1, accountId);
...
    
      
      

不是在很多(或者在这个例子中是 80)个不同地方重复这些代码,而是将构建连接的实际代码移到它能被共享的地方。getConnection()方法按以下所示进行实现:


public Connection getConnection() throws SQLException {
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    String database = DbParameters.getDbName();
    String user = DbParameters.getDbUserName();
    String password = DbParameters.getDbPassword();
    return DriverManager.getConnection ("jdbc:oracle:oci8:@"+database, user, password);
}
    

在 Martin Fowler 的讨论"重分解"(Refactoring)的书 [Fowler1999] 中,这被称为"抽取方法"(Extract Method)重分解。这种改动不会引入大量额外代码,但却增添了很多价值。不只是代码的共享更容易,而且代码的阅读和理解也更容易。通过减小方法的大小(和复杂性),附加上了更大的语义意义(在新方法的名称的帮助下),这使得代码更易于理解和支持。额外的重分解将使getConnection()方法甚至更加有用。通过由另一个更易于共享的类来负责实现这个方法,更多地减少了代码重复。

如果这块代码一致地使用getConnection()方法,则引用定制构建(custom-built)的连接池是容易的:


public Connection getConnection() throws SQLException {
    return getConnectionPool().getConnection();
}
    

专有连接池用法的改变不要求对使用了getConnection()方法的代码作任何修改。当将代码移到象 IBM WebSphere Application Server 这样的平台上时,通过再次更新getConnection()方法,就可以容易地使用标准 JDBC 连接池:


public Connection getConnection() throws SQLException {
    return getDataSource().getConnection();
}
    

要完全支持任何一种连接池机制都额外需要一些代码。要点是将复杂性隐藏在抽象后面,一块代码永远不必看到它。

最佳实践方法:应用采用分层结构

分层是一种方式,它将应用的代码归组到相关的子部分。每一层包含特定于某些功能和目的的代码。分层的一个典型示例是所谓的模型/视图/控制器(Model/View/Controller(MVC))体系结构,它把模型、视图和控制分散成层。

层有很多优点。最明显的优点是角色和责任的分离。内容设计者负责 JSP,Java 开发者负责 servlet、JavaBeans™ 和 EJB。然而,这种体系结构还有更深层的东西:当对应用作变动时(移植就是变动的一个例子),通过把整个大块代码分成孤立的更易于理解的小块代码,分层带来了帮助;而且,这些小块的代码易于测试。当分层工作做得很好时,应用的整个业务逻辑常常可以不用修改或只做少量修改即可移植。

很多人不理解的是控制层该做什么。JSP 提供视图,servlet 提供控制,而 Java 类或 EJB 提供模型,这说起来容易。但“控制”该走多远呢?这里是 ATG Dynamo FormHander 把控制和模型的区别弄得模糊不清的一个示例:


public boolean handleSubmit(
    DynamoHttpServletRequest request, DynamoHttpServletResponse response)
throws ServletException, IOException{
    try {
        
        
        BankAccount source = findAccount(getSourceId());
        BankAccount destination = findAccount(getDestinationId());
        source.debit(amount);
        destination.credit(amount);
        response.sendLocalRedirect(getSuccessURL(),request);
   } catch (InsufficientFundsException e) {
        response.sendLocalRedirect(getFailureURL(),request);
        return false;
   }
    return true;
}
    
      
      

请原谅事务控制的明显缺乏和遗漏了异常处理。问题是在此捕获业务处理部分。这个方法所描述的是把资金从一个银行帐户转到另一个银行帐户的过程。这是一个业务流程,不是控制。业务流程实际应是属于模型层的一部分。

在 MVC 中,“控制”是视图和模型之间的通信的过程。控制层应处理抽取数据、把这些数据转换成模型能够理解的一些东西、调用模型上的行为和把该行为的结果转换成视图可以使用的一些东西。“Facade” 模式 [Gamma1995] 是封装业务流程的一种流行方式,从而将行为从控制器中除去。


public boolean handleSubmit(
    DynamoHttpServletRequest request, DynamoHttpServletResponse response)
throws ServletException, IOException{
    try {
        
        
        getTeller().transfer(getSourceId(), getDestinationId(), getAmount());
        response.sendLocalRedirect(getSuccessURL(),request);
   } catch (InsufficientFundsException e) {
        response.sendLocalRedirect(getFailureURL(),request);
        return false;
   }
    return true;
}
    
      
      

“teller”对象负责提供响应 transfer(...) 消息的行为。作为行为的一部分,人们希望 InsufficientFundsException 会被抛出(控制器必须将这个结果转换成视图能够理解的一些东西)。这个方法的实现是以前使用的抽取方法重分解的另一个应用。主要的一点是业务流程行为现在不再位于控制层,它属于这里:位于模型层。

诚然,这个示例非常简单,但请考虑到 Facade 现在也负责提供事务边界。使用 Facade 有很多好处,包括重复代码的减少和语义耦合的降低,因为控制层不需要再了解模型层。更令人感兴趣的可能是 Facade 的使用提供了一条到 EJB 会话 bean 的非常自然的移植路径(请参阅 [Brown2000] 和 [Sun2001])。

使用 Facade,有一个效果是减少了从控制器到模型的耦合。然而,我们试图保护的并不是控制器。业务模型和业务流程可能是一个应用中最易于移植的部分。通过引入 Facade,保护业务层免于控制器的细节。只须对业务层作少量改动或不用改动就可以引入一个备用控制机制。

把 FormHandler 移植到 IBM WebSphere Application Server 的 servlet 需要对控制层作相当多的改动,但不需要修改模型层。


public void doPost(HttpServletRequest request, HttpServletResponse response) {
    int sourceId = Integer.parseInt(request.getParameter("sourceId"));
    int destinationId = Integer.parseInt(request.getParameter("destinationId"));
    double amount = Double.parseDouble(request.getParameter("amount"));
    try {
        
        
        getTeller().transfer(sourceId, destinationId, amount);
        getServletContext().getRequestDispatcher("success.jsp").forward(request, response);
    } catch (InsufficientFundsException e) {
        getServletContext().getRequestDispatcher("failure.jsp").forward(request, response);
    }
}
    
      
      

再一次,遗漏了一些异常处理。控制器仍然非常简单,这使得移植过程比应用一些方言来转换数据和调用行为多不了多少。

最佳实践方法:编写和维护自动测试

这里是一个重要问题:您打算怎样来知道移植是否有效?简单的回答是您只得进行测试。手工测试会用去很长时间,而且容易出错。手工测试有它适合的地方。自动测试是一种有价值的机制,它让软件开发者在一个非常频繁的基础上验证他们的工作。

自动测试可有很多形式。 JUnit 测试框架是一个非常简单的、基于 Java 的测试框架,它是完全免费的(在开放源代码许可证下)。“布满测试(Test-infested)”的 JUnit 用户在开发有待测试的代码之前开发测试。对老套的人而言,这听起来很奇怪。但它行得通。有了适当的原则,您就可以保证拥有适当的测试代码。

如果您现在没有合适的测试代码,在开始移植之前,做一些提供测试的工作将增加很大价值并节省大量时间。自动测试可以作为对进度进行衡量的一种机制。如果测试案例有 90% 都能在移植后的代码上运行,则您很可能完成了将近 90%。更重要的是,测试使您确信代码按所期望的运行。而且,测试能帮助您把代码放到上下文中;如果测试是最新的,则它们有这些代码希望如何被使用的文档。

结束语

在这个日新月异的世界中,移植是一项要考虑的重要事情。当我们全力以赴于现有标准并专注于专有解决方案时,我们需要考虑这些解决方案对将来的影响。可能会出现新的标准和产品来解决这些问题。需要采取策略,使变动可能给您的应用带来最小的影响。充分地分层的代码能生存更长时间,对变动也有更好的弹性。

想使不可避免的移植变得更容易,分层体系结构和抽象是您可以使用的最佳工具。当然,移植要超越这些;竞争的应用服务器之间的移植仅仅是应用将经受的变动的一种形式。其它元素的变动,例如数据库和中间件以及业务需求,将会在您的应用的生存期间发生。预先为不可避免的变动作准备,这些工作将从长远给您回报。

如果您认为没有时间来做这些事,那您就错了:您没有时间来不做这些事。测试、分层和抽象都给当前带来很大开销,但随着时间的推移,您能节约时间和金钱。更易于理解的代码也是更易于变动、更易于扩展、更易于移植的代码。这些事情都将发生,为什么不做好准备呢?



参考资料

[Fowler1999] Fowler , Martin 的 Refactoring: Improving the Design of Existing Code。大众读物:Addison-Wesley,1999

[Gamma1995] Gamma , Erich 等人的 《设计模式》。Addison-Wesley,1995

[Brown2000] Brown , Kyle 的“ LayeredArchitectures for EJB Systems”,VisualAge 开发者园地

[Sun2001] “ 会话 Facade”,Sun Java Center J2EE 模式



关于作者

Wayne Beaton是 IBM Software Group 的 AIM 服务的高级软件顾问。Wayne 的多种角色使他参与了很多有趣的工程,从 WebSphere Skills Transfer and Migration 编程到总顾问。Wayne 喜欢在业余时间说服人们相信极端编程、重分解和单元测试确实行得通。可通过 wbeaton@ca.ibm.com与 Wayne 联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款