hk2000c技術專欄

          技術源于哲學,哲學來源于生活 關心生活,關注健康,關心他人

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            111 隨筆 :: 1 文章 :: 28 評論 :: 0 Trackbacks

          克服J2SE 1.3 ~ 1.4不兼容問題
          --從反射API和ANT獲得幫助



          概要
          ??? 如果你要實現(xiàn)JavaAPI中的一個,那么可能是件比較痛苦的事情。你經常會需要實現(xiàn)許多交叉依賴的接口。對新特性的需求促成了升級現(xiàn)有的JavaAPI,這就造成了提供這些API的供應商對他們的相關實現(xiàn)不斷的升級以維持相關功能。隨著這些API的升級更改越來越頻繁,API代碼的不兼容使你不得不分別維護新舊版本的代碼庫。這直接到導致了你維護成本和難度的增加。本文演示了解決此問題的技術,揭示了如何僅使用一個代碼庫編譯不同JavaAPI版本的代碼。



          ??? 現(xiàn)在非常多的API被加入到到Java的標準庫中,比如JDBC。這樣做的好處是,Java可選包在部署時不必被綁定到相關的部署應用中去。這些API由專門的專業(yè)開發(fā)小組實現(xiàn),在實際的使用當中這些API變得越來越受歡迎,使用的深度及廣度也在不斷的增加。但是有時候對一些API升級會變得使一些類及方法不可用。開發(fā)小組寧愿讓這些API包成為可選組件而不是作為Java標準支持庫的形式來發(fā)布。但是一旦加入標準庫中的API包,就像是和用戶簽定了終生契約,想再成為可選包是不可能的。所以作為用戶的你,可能會突然發(fā)現(xiàn)你一下子自己的代碼庫變成了不兼容的2個代碼庫,一個是使用新API的代碼庫,另一個是使用舊API的代碼庫。你可能會以為情況不像你想象的那樣糟糕。我這里舉一個簡單的例子。J2SE1.4中由于對JDBC中的一些API的升級使的java.sql.Connection 不能同時被1.3 及 1.4 版本編譯通過。你可能會遇到我這樣的困境:我可能需要實現(xiàn)java.sql.Connection這個接口,但是我的代碼需要同時通過1.3 及1.4 得編譯。但是我不想同時維護2個版本的代碼庫。所以我開始尋找更好的解決方法。
          ??? 如果你依賴于javac來編譯你的應用的話,那么很不幸,Java著名的一次編寫,到處運行(WORA)并不包括WOCA(一次編寫,到處編譯^_^;)。
          不過別太沮喪,編碼的反射技巧以及編譯的Ant技巧是你能夠安然過關。我能夠僅僅使用一組Java文件以及Ant工具,就能使一個版本同時編譯
          在1.3 和1.4 版本下面。別急,在我結識解決辦法之前,讓我先詳細的解釋一下問題的描述。

          可憐人的連接池(PS:Poor man's connection pool ,很有意思的一句話)
          ??? 兩年前,我的公司需要一個連接池,但是又不肯出錢買一個。當時并沒有什么免費的東東可以使用,所以我們自己寫了一個連接池。為了能更好的跟蹤在整個應用中連接的情況,我們寫了一個com.icentris.sql.ConnectionWrapper類,它實現(xiàn)了java.sql.Connection 接口以及其他的一些包裝類(實現(xiàn)了另外的一些的java.sql 接口)。這些包裝類僅僅是跟蹤我們應用中的數(shù)據庫使用,以及通過方法調用真正的
          數(shù)據庫資源。
          ??? 當J2SE1.4來的時候,我們自然而然的想到升級我們提供給客戶的應用,使這些應用的性能得到很多提升。當然,我們也需要保留1.3版本,因為有些客戶根本不需要升級到1.4。我們氣惱的發(fā)現(xiàn),如果我們不修改,我們的ConnectionWrapper 以及其他JDBC封裝類根本通不過J2SE1.4的編譯。
          ??? 為了文章的簡明,我通過使用ConnectionWrapper 這個類來演示我對所有其他不能夠通過J2SE1.4的類所使用的技術。如果我按照新的API標準,那么我不得不添加幾個方法到ConnectionWrapper中去,接下來2個大問題擺在了面前:
          1.因為我的包裝類需要經歷方法調用,我將不得不調用在J2SE1.3 sql類中并不存在的方法。
          2.因為一些新的方法涉及到一些新出現(xiàn)的類,我將不得不在編譯中面對那些在J2SE1.3中并不存在的類。

          反射提供了援助
          一些代碼可以很方便的解釋第一個問題。但是我的ConnectionWrapper 封裝了java.sql.Connection , 所有的我的例子
          依賴于在構造方法中的變量 realConnection :

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

          ??? 為了看清楚我怎么做到解決版本不兼容問題,讓我們仔細看一下setHoldability(int)(這個在J2SE1.4被聲明的新方法)
          public void setHoldability(int holdability) throws SQLException {
          ??? realConnection.setHoldability( holdability );
          ? }

          ??? 很不幸,這個方法在J2SE1.3中顯然通不過編譯,這就陷入了2難的尷尬境地。為了解決這一情況,我假定setHoldability() 將只會在J2SE1.4
          下面被調用,所以我使用了反射機制來調用該方法。

          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);
          ??? }
          ? }

          ??? 現(xiàn)在我有了setHoldability() 方法,因此能順利通過J2SE1.4的編譯。原理是我并不直接調用J2SE1.3中間java.sql.Connection并不存在的方法,
          而是轉為通過讓setHoldability調用callJava14Method這個通用方法來調用,然后在一個SQLException 里封裝所有的異常。這樣就達到我預期的效果。
          現(xiàn)在所有的在J2SE1.4中新方法都工作的很好,在J2SE1.3的老版本下也能順利編譯而且工作正常。現(xiàn)在我來著手解決第二個問題。
          就是如何在應用中能夠找到一個方法能夠使用J2SE1.3中并不存在的新的類。

          Ant 是答案
          在J2SE1.4中,java.sql.Connection 依賴于一個新的類java.sql.Savepoint。因為這個類在java.sql 包中,所以你不可能把它加入到J2SE1.3中去。Java不允許任何的第三方擴展包加入它的核心包(java.* 以及 javax.* )中去。 因此挑戰(zhàn)來了,在J2SE1.4下調用這個新的java.sql.Savepoint 類,但同時需要代碼能夠在J2SE1.3下面得到編譯以及能夠運行。很簡單,不是嗎?所有回答"Yes"的人都會得到一個榛仁巧克力餅(PS:哈哈,我回答了,可是沒有:P)。至少現(xiàn)在我找到了答案,使問題變得很簡單了。
          ? 首先我插入了下面一條有條件的import語句
          // Comment_next_line_to_compile_with_Java_1.3
          ? import java.sql.Savepoint;

          ??? 然后我找到了一個能夠在J2SE1.3下面注釋掉import的方法。非常簡單,使用如下Ant 語句就可以了:
          <replace>
          ??? <replacetoken>Comment_next_line_for_Java_1.3&#010;</replacetoken>
          ??? <replacevalue>Comment_next_line_for_Java_1.3&#010;//</replacevalue>
          ? </replace>

          ??? 這個Ant 的 replace 標簽 有好幾個標簽選項,在以后我給出的全部例子里有很多。在這里面最重要的是使用<replacevalue>來替換<replacetoken> 。&#010;在XML里面的意思是換行。在J2SE1.4下,沒什么會發(fā)生, 但是在J2SE1.3下面一個import聲明被注釋掉了。
          // Comment_next_line_to_compile_with_Java_1.3
          ? //import java.sql.Savepoint;

          ??? 但是我在代碼中Savepoint仍在使用public Savepoint setSavepoint(String name) throws SQLException { . . .}。不過我只在J2SE1.4使用這些方法類,在J2SE1.3中只要能編譯就可以了。我發(fā)現(xiàn)只要我有一個我自己的Savepoint 類在我的包中,我的代碼就能夠通過編譯,而且不用任何的import包。但是我又要同時在這條import 語句不被注釋的同時我自己的Savepoint類被忽略掉。因此我造了一個空的com.icentris.sql.Savepoint類,這個可能(除了JavaDoc)是最短的有效類:
          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 {}

          ??? 在J2SE1.4下我能夠正確的import java.sql.Savepoint類,而在J2SE1.3下面Ant注釋了這條import語句。因此這個Savepoint就被替換成了我這個包里面寫的一個空的Savepoint類。所以我現(xiàn)在就能加入任何引用到Savepoint類的方法,同樣的在這些新方法中使用剛才所說的反射方法。
          // 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);
          ??? }

          ??? 現(xiàn)在我所要做的就是能夠使Ant 識別 J2SE1.3版,然后能夠使這條import 語句被注釋掉。
          <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&#010;</replacetoken>
          ????? <replacevalue>Comment_next_line_for_Java_1.3&#010;//</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&#010;//</replacetoken>
          ????? <replacevalue>Comment_next_line_for_Java_1.3&#010;</replacevalue>
          ??? </replace>
          ? </target>

          ??? 注意編譯目標在調用doJava13Tweaks的前后都調用了undoJava13Tweaks。如果萬一javac編譯失敗的話,我們可以恢復以前的編譯版本。

          你沒有必要同時維護2個應用實現(xiàn)
          ??? 對于Java來說,新的API升級所帶來的新的方法以及新的類/接口并不是新鮮事。一般而言,加入的新方法以及新的類的同時,會考慮到向上兼容的問題來照顧老API用戶。但是當升級的API屬于Java核心包內時,就會很麻煩。因為Java不允許對這些核心包的任何的外在更改或者是增加。通常這會引起針對不同版本API而維護不同版本代碼樹的需要。但是,就像上面的例子所演示的那樣,你只要維護一棵代碼樹就能夠在不同的版本的API下,編譯運行。這個反射的API允許你調用并不存在的方法,而Ant能通過識別不同的Java編譯版本而對相應的import包進行調整。雖然上面的所舉的例子僅僅是一個簡單的演示,但是在實際工作當中,利用這些簡單的技術,解決了許多J2SE1.4和J2SE1.3的版本問題。我相信通過這些技術,你可以在頻繁的Java版本升級中不必為同時維護兩棵代碼庫而煩惱。

          ?


          關于作者:
          Sam Mefford是iCentris的首席架構設計師。對于系統(tǒng)的兼容性重視程度,Sam Mefford是放在第一位的。他帶領的團隊致力于使用一個代碼庫
          向眾多的客戶公司提供應用發(fā)布方案。這些部署方案使用的應用服務器有Tomcat,Weblogic, Resin, Orion以及 Websphere;在數(shù)據庫方面有
          Oracle,PostgreSQL, MySQL,以及 Informix;以及多個Java運行期環(huán)境。

          譯者: SpikeWang (CSDN ID:hk2000c)

          東華大學計算機系畢業(yè),現(xiàn)在同濟大學攻讀軟件工程碩士學位。致力于J2EE方面的企業(yè)級應用開發(fā)以及研究工作。


          About Copyright:
          原文章版權屬于作者 Sam Mefford
          譯文版權屬于譯者及原文作者共同所有,歡迎轉載,但要注上譯者及原文作者。


          參考資源:
          The API for java.sql.Connection (J2SE 1.3):
          http://java.sun.com/j2se/1.3/docs/api/java/sql/Connection.html

          The API for java.sql.Connection (J2SE 1.4):
          http://java.sun.com/j2se/1.4.2/docs/api/java/sql/Connection.html

          The JDBC API:
          http://java.sun.com/products/jdbc/

          Java Core Reflection參考概要:
          http://java.sun.com/j2se/1.3/docs/guide/reflection/spec/java-reflectionTOC.doc.html

          the Reflection API指南:
          http://java.sun.com/docs/books/tutorial/reflect/

          The Javadoc (java.lang.reflect):
          http://java.sun.com/j2se/1.3/docs/api/java/lang/reflect/package-summary.html

          ?

          ?

          ?

          ?


          posted on 2003-10-03 22:27 hk2000c 閱讀(156) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網站導航:
           
          主站蜘蛛池模板: 凯里市| 山阴县| 青海省| 墨竹工卡县| 岑溪市| 安仁县| 广汉市| 民县| 广饶县| 苏尼特左旗| 海林市| 舟曲县| 老河口市| 冀州市| 哈巴河县| 炉霍县| 抚顺市| 资源县| 台北市| 常熟市| 屯昌县| 元氏县| 浦东新区| 瓮安县| 漾濞| 灵丘县| 无锡市| 仪征市| 富阳市| 开原市| 宜春市| 常熟市| 大同县| 万全县| 晋州市| 砚山县| 莱阳市| 金山区| 玛多县| 增城市| 福海县|