今日更新
技术新闻
精彩专题
IBM软件技术专区
微软开发专区
技术文档中心
编程语言
网络通信
网络安全
LINUX/UNIX
软件工程与管理
数据库开发
WEB开发
企业应用与开发
移动开发
资源中心
原创专栏
开放系统世界
人才与培训
技术天地论坛
厂商列表
社区推荐

· 女性身体内部人体受孕..
· 十部顶级的变态与情色..
· 感情放纵让我毁了两个..
· 当我撞见姐姐和男友在..
· 卖淫少女惨遭泄愤民工..
· 偷拍街上的走光mm绝对..
· 百度打击google的广告
· 港娱乐圈与黑社会揭秘
赛迪网>>技术应用>>J2SE
关键字: J2SE
来  源: 赛迪网
克服J2SE 1.3-1.4 的不兼容性
作者:Sam Mefford 著 陈姣姣 翻译 发文时间:2003.11.10
概要

实现Java 众多API中的一个是一件困难的工作。你必须经常实现许多相互依赖的接口。对新特征的需求促进了最新Java API的创建,投资商们必须不断的更新他们的实现以跟上时代的需要。当复杂性和持续修改可以理解甚至被看好时, API 版本的不兼容性迫使你为更新的版本保留独立的代码库,从而N次方增加你的受挫等级。本文演示了如何克服接口版本的不兼容性,制作可为多个API版本编译的代码库的进程。

Java 已经添加了无数个API,像Java 数据库连接(JDBC),到它的标准库系列上。这可以帮助更广泛的用户使用API,因为可选软件包不必捆绑到配置上。对于编写这些流行的API的工作组来说,使用的越多他们的付出就越有价值。但是,当更新的Java版本运载最新的API,而最新的API依赖的类和方法是以前的Java版本所不能实现的时候,这些工作组希望他们的API仍然是可选的(与包括在Java的标准库系列内相反)。这样你忽然必须要保留实现的两个版本——一个编译老的API,另一个编译新的API。这就是发生在Java2平台标准版(J2SE)1.4中的JDBC API身上的事情。因为修改了JDBC API, java.sql.Connection 的一个实现不能在J2SE 1.3 和 1.4版本下编译。

你可能发现你自己也处于与我一样的困境中:我需要实现JDBC 接口如 java.sql.Connection,但是我的代码需要在J2SE 1.3 和1.4下编译。我不想为J2SE 1.3 和1.4保留不同的源文件,所以我寻找更好的解决方法。

不幸的是,当你需要Java编译器来完成编译时著名的WORA (一次编写,各处运行)不包括WOCA (一次编写,各处编译)。幸运的是,使用Reflection API对代码做一些修改并且使用Ant 对编译做一些修改可以帮你走出困境。我能有一组.java 源文件和Ant来帮助我在J2SE 1.3 和 1.4两者之上编译代码。Ant允许我运行时修改.java文件,                               正确修改以便与编译时使用的Java版本兼容。但是在我阐述整个解决方案之前,我必须得说明整个问题。

穷人的连接池

两年前,我的公司需要一个JDBC连接池但是又买不起。那个时候,我们找不到好的免费的备选的东西,所以我们编写了一个内部的连接池。为了更好的追溯连接在整个我们的应用程序上是如何使用的,我们创建了com.icentris.sql.ConnectionWrapper,它实现java.sql.Connection和一些其他的可实现其他java.sql接口的包装者类。 包装者类只追踪我们应用程序内的数据库的使用并且传递方法调用到真正的JDBC资源处。

当 J2SE 1.4 出来的时候,我们自然想要将一些我们的客户端程序移到它上面,这样他们就能从J2SE 1.4的许多改进中获益。但是,当然,我们仍然需要支持J2SE 1.3,因为那些客户端看不出有什么理由需要升级。我们气愤的是, ConnectionWrapper和我们其他的JDBC包装者类不修改就不能在J2SE 1.4上编译。为了保持文章的简洁,我使用ConnectionWrapper来演示我应用到所有不能同时在J2SE 1.3和 1.4下编译的类的技巧。 为了遵循最新的JDBC API,我不得不添加几个方法到ConnectionWrapper上,这造成了两个大问题:

1.因为我的包装者类需要通过方法调用,我不得不调用J2SE 1.3 sql 类中没有的方法。

2.因为一些新方法依赖于新的类,我不得不依赖于J2SE 1.3 中没有的类。

Reflection 来拯救

一些代码样品最适合解释第一个问题。因为我的ConnectionWrapper包裹了java.sql.Connection,所有我的样品依赖于构造器中的realConnection实际变量 (粗体) 组:

private java.sql.Connection realConnection = null;
  
  public ConnectionWrapper(java.sql.Connection connection) {
    realConnection = connection;
  }


为了看清楚我在没有不兼容问题的情况下做过什么,我们看看setHoldability(int) (到J2SE 1.4中的java.sql.Connection 的新方法):

public void setHoldability(int holdability) throws SQLException {
    realConnection.setHoldability( holdability );
  }


不幸的是,该代码在J2SE 1.3下不能编译因为 java.sql.Connection 没有我在J2SE 1.3才能调用的setHoldability()方法。但是要在J2SE 1.4下编译,我必须有一个setHoldability()方法可正确的实现该API. 要解决这个catch-22,我假设我的setHoldability() 方法只能在J2SE 1.4下编译,所以我可以使用Reflection API来调用该方法:

public void setHoldability(int holdability) throws SQLException {
    Class[] argTypes = new Class[] { Integer.TYPE };
    Object[] args = new Object[] {new Integer(holdability)};
    callJava14Method("setHoldability", realConnection, argTypes, args);
  }

  public static Object callJava14Method(String methodName, Object instance,
  Class[] argTypes, Object[] args)
    throws SQLException
  {
    try {
      Method method = instance.getClass().getMethod(methodName, argTypes);
      return method.invoke(instance, args );
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
      throw new SQLException("Error Invoking method (" + methodName + "): "
      + e);
    } catch (IllegalAccessException e) {
      e.printStackTrace();
      throw new SQLException("Error Invoking method (" + methodName + "): "
      + e);
    } catch (InvocationTargetException e) {
      e.printStackTrace();
      throw new SQLException("Error Invoking method (" + methodName + "): "
      + e);
    }
  }


现在我有了setHoldability()方法,所以我可以在 J2SE 1.4下编译。我不直接调用我的java.sql.Connection 上以前不存在的方法,所以我能在J2SE 1.3下编译。我的 callJava14Method()方法使用 Reflection API调用该方法,然后包裹任何错误在SQLException里面,因为SQLException就是我给出的异常的全部。我将该策略用于所有新的J2SE 1.4方法,调用包裹了的方法,然后在 1.3下编译。现在我只需要解决第二个问题并且找出一条方法来依赖J2SE 1.3中不存在的类。

Ant就是答案

在J2SE 1.4中,java.sql.Connection依赖于新类java.sql.Savepoint。因为这个新类设置在 java.sql软件包内,你不可能将它添加到J2SE 1.3上。Java不允许添加任何第三组织的东西到java.* 或者 javax.*软件包中的核心类系列上。所以这也是对我的挑战:使用新的java.sql.Savepoint类来编写我的代码这样它就可以在J2SE 1.4下运行, 然后确保代码可在J2SE 1.3下编译,实际上,这个类在J2SE 1.3中不存在。简单,对吧?说“YES”的人都品行良好。好,至少,它简单得现在我已经找到答案了。

首先,我将下列条件输入包括进来:

// Comment_next_line_to_compile_with_Java_1.3
  import java.sql.Savepoint;


然后为 Ant找到一条方法:当在J2SE 1.3下编译时可注释该输入。 简化之后, Ant 脚本核心部分为:

<replace>
    <replacetoken>Comment_next_line_for_Java_1.3
</replacetoken>
    <replacevalue>Comment_next_line_for_Java_1.3
//</replacevalue>
  </replace>


该 Ant 标记有几个选项——在下面的我的完整范例中你会看到更多——但是最重要的部分就是我搜索 然后用取代它。“

” 是用于"newline"的XML 实体。当在J2SE 1.4下编译时,Ant对源文件不做任何修改;并且在 J2SE 1.3下,重要语句被注释:

// Comment_next_line_to_compile_with_Java_1.3
  //import java.sql.Savepoint;


但是在必须依赖Savepoint 的类的体中仍有代码:

public Savepoint setSavepoint(String name) throws SQLException { . . .


再次,我只能期望可在J2SE 1.4下使用这些新方法,这样他们不必在J2SE 1.3下运行; 他们只需要编译。我发现:如果我的软件包中有一个Savepoint 类,我的代码不需要输入语句就可以编译。但是当输入语句没有注释的话(在J2SE 1.4编辑下),我的Savepoint 类会被忽略因为存在更特殊的输入。所以我创建了我自己的哑元类com.icentris.sql.Savepoint,它(java文章排斥它)可能是最短的有效类 :

package com.icentris.sql;

  /** Dummy class to allow ConnectionWrapper to implement java.sql.Connection
   * and still compile under J2SE 1.3 and J2SE 1.4. When compiled
   * under J2SE 1.3, this class compiles as a placeholder instead of the
   * missing java.sql.Savepoint (not in J2SE 1.3).  When compiled
   * under J2SE 1.4, this class is ignored and ConnectionWrapper uses the
   * java.sql.Savepoint that is new in J2SE 1.4.
   */
  public class Savepoint {}


在J2SE 1.4下,我现在可以正确的输入java.sql.Savepoint。在J2SE 1.3下, Ant 注释输入行,这样我的代码中引用的 Savepoint正好在同一个软件包处于哑元类的位置。 现在我可以添加所有引用Savepoint 的方法并且仍然可以使用前面解释过的Reflection 技巧:

// Comment_next_line_to_compile_with_Java_1.3
  import java.sql.Savepoint;

  . . .
    public Savepoint setSavepoint() throws SQLException {
      Class[] argTypes = new Class[0];
      Object[] args = new Object[0];
      return (Savepoint) callJava14Method("setSavepoint", realConnection,
      argTypes, args);
    }

    public Savepoint setSavepoint(String name) throws SQLException {
      Class[] argTypes = new Class[] { String.class };
      Object[] args = new Object[] { name };
      return (Savepoint) callJava14Method("setSavepoint", realConnection,
      argTypes, args);
    }

    public void rollback(Savepoint savepoint) throws SQLException {
      Class[] argTypes = new Class[] { Savepoint.class };
      Object[] args = new Object[] { savepoint };
      callJava14Method("rollback", realConnection, argTypes, args);
    }

    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
      Class[] argTypes = new Class[] { Savepoint.class };
      Object[] args = new Object[] { savepoint };
      callJava14Method("releaseSavepoint", realConnection, argTypes, args);
    }


现在我需要的就是:用来探测J2SE 1.3 的我所有的Ant compile目标,以及当有人试图使用J2SE 1.3编译ConnectionWrapper时在运行过程中对输入行的注释:

<target name="compile">
    <antcall target="undoJava13Tweaks" />
    <antcall target="doJava13Tweaks" />
    <javac srcdir="src" destdir="WEB-INF/classes" debug="on">
      <classpath>
        <fileset dir="WEB-INF/lib">
          <include name="*.jar"/>
        </fileset>
      </classpath>
    </javac>
    <antcall target="undoJava13Tweaks" />
  </target>

  <target description="Find out if we're being compiled on Java 1.3"
  name="isJava13">
    <echo message="java.specification.version=[${java.specification.version}]"/>
    <condition property="isJava13">
      <equals arg1="${java.specification.version}" arg2="1.3" />
    </condition>
  </target>

  <target description="There are a couple tweaks I have to do to compile under Java 1.3"
    name="doJava13Tweaks" depends="isJava13" if="isJava13">
    <echo message="This is Java 1.3, doing Tweaks!" />
    <replace dir="src/com/icentris/" summary="true">
      <include name="sql/ConnectionWrapper.java" />
      <replacetoken>Comment_next_line_for_Java_1.3
</replacetoken>
      <replacevalue>Comment_next_line_for_Java_1.3
//</replacevalue>
    </replace>
  </target>

  <target description="Let's undo Java 1.3 tweaks" name="undoJava13Tweaks">
    <replace dir="src/com/icentris/" summary="true">
      <include name="sql/ConnectionWrapper.java" />
      <replacetoken>Comment_next_line_for_Java_1.3
//</replacetoken>
      <replacevalue>Comment_next_line_for_Java_1.3
</replacevalue>
    </replace>
  </target>


注意compile目标在doJava13Tweaks之前之后都调用undoJava13Tweaks。 这是为了防止javac编辑目标时失败,并且我们拥有没有清除掉的剩余替代品。

不需要保留两个实现

对于一个新的API版本来说将新方法和新的类(或者接口)包括在内是很普遍的。如果实现器能够实现新的API并且仍然提供对更老版本的后台兼容,那么这对实现器来说也不是一个重要的障碍。但是,当API是核心Java类的一部分时,API的改动会引起大得多的困难,因为 Java不允许任何外在的修改或者任何java.*软件包的添加物。通常,这会导致需要支持修改了的API的各个版本的不同源代码树。

但是,就像上文演示的一样,你可以在两个Java 版本上都编译一个源代码树。Reflection API 允许调用不存在的方法,并且Ant允许输入以适时修改,以便支持对在编译 Java 版本下不存在的类的依赖。尽管上述范例为了演示方便简化了,但我成功地使用这些或者类似的技巧来解决与同时支持J2SE 1.3 和1.4有关的不同问题。既然这么多的Java APIs都不断的更新,那么你可以使用这些技巧来帮助避免维护两个代码库的非想望的需求。

关于作者

作为iCentris的总设计师,Sam Mefford 将兼容性摆在第一位。他领导的团队使得一个代码库为许多公司所采用;一个应用服务器包括Tomcat, WebLogic, Resin, Orion,和WebSphere;一个数据库包括Oracle, PostgreSQL, MySQL, 和 Informix;一个代码库可用在多个Java运营环境内。

本文译自:http://www.javaworld.com/javaworld/jw-09-2003/jw-0926-overcome.html

(责任编辑:赵纪雷)




赛迪网推出“IT博客”,花不到一分钟就完成注册
评论】 【推荐】 【 】 【打印】 【关闭

·Linux专区· ·黑客攻防·
· Linux下添加硬盘、分区、格式化任务详解
· FreeBSD服务器的安装与优化之优化篇
· 初学者入门:FreeBSD服务器的安装与优化
· 金企鹅杯两岸四地开源软件大赛圆满结束
· 如何提高Linux系统安全性的十大招数
· 构筑Linux防火墙之为个人用户设置防火墙
· 谁更安全?黑客眼中的防火墙与路由器
· 识破骗局 练就识别QQ活动真伪火眼金睛
· 应用安全大有可为:目的、挑战、总结
· 道高一尺魔高一丈:安全防御的动感魅力
· 警惕网络“内”院起火 积极谋求安内之路
· HHCTRL漏洞被黑客利用 疯狂传播木马
·中国信息化· ·成功案例·
· ERP普及化是饮鸩止渴 精细化才是应用之道
· 赛门铁克第八期《互联网安全威胁报告》解析
· 抢食“数字工商” 国产中间件杀出血路
· 从IBM等操作系统的发展看软件创新的启示
· 服务成就蓝色快车 品牌是怎样炼成的?
· 三大技术应用大会合为一体甲骨文上演三重奏
· 南阳教育城域网 拆掉学校间的“围墙”
· 金算盘助申意美步入信息化快车道
· 不为人知的索尼信息化 谁是幕后英雄?
· InforBus/Q在穗高速路联网收费系统中的应用
· J2EE构建最新金融理念和运作模式的网上银行
· 食品安全令人担心 信息化能否保驾护航
*姓  名: 更多资料 了解方案 认识厂商
*单位名称:
*联系电话:
*电子邮件:
    
◆ 相关文章   ◆ 站内热点推荐
· 使用select-新IO库提速你的服务器
· 设计Palm网络数据通信程序
· 网管员论坛
· 开发者之家
· WLAN无限未来
· 我是如何掉进C#的……
· 中国“人件”非正式调查

   
合作网站: IBM dW中国网站 LinuxAID 软件工程专家网 中国系统分析员 UMLChina MATRIX Mobile2008 JavaResearch 华储网 UML软件工程组织 中国JAVA手机网 JAVA中文站 金山在线 海量科技