概要 如果你要實現JavaAPI中的一個
那麼可能是件比較痛苦的事情
你經常會需要實現許多交叉依賴的接口
對新特性的需求促成了升級現有的JavaAPI
這就造成了提供這些API的供應商對他們的相關實現不斷的升級以維持相關功能
隨著這些API的升級更改越來越頻繁
API代碼的不兼容使你不得不分別維護新舊版本的代碼庫
這直接到導致了你維護成本和難度的增加
本文演示了解決此問題的技術
揭示了如何僅使用一個代碼庫編譯不同JavaAPI版本的代碼
現在非常多的API被加入到到Java的標准庫中
比如JDBC
這樣做的好處是
Java可選包在部署時不必被綁定到相關的部署應用中去
這些API由專門的專業開發小組實現
在實際的使用當中這些API變得越來越受歡迎
使用的深度及廣度也在不斷的增加
但是有時候對一些API升級會變得使一些類及方法不可用
開發小組寧願讓這些API包成為可選組件而不是作為Java標准支持庫的形式來發布
但是一旦加入標准庫中的API包
就像是和用戶簽定了終生契約
想再成為可選包是不可能的
所以作為用戶的你
可能會突然發現你一下子自己的代碼庫變成了不兼容的
個代碼庫
一個是使用新API的代碼庫
另一個是使用舊API的代碼庫
你可能會以為情況不像你想象的那樣糟糕
我這裡舉一個簡單的例子
J
SE
中由於對JDBC中的一些API的升級使的java
sql
Connection 不能同時被
及
版本編譯通過
你可能會遇到我這樣的困境
我可能需要實現java
sql
Connection這個接口
但是我的代碼需要同時通過
及
得編譯
但是我不想同時維護
個版本的代碼庫
所以我開始尋找更好的解決方法
如果你依賴於javac來編譯你的應用的話
那麼很不幸
Java著名的一次編寫
到處運行(WORA)並不包括WOCA(一次編寫
到處編譯^_^;)
不過別太沮喪
編碼的反射技巧以及編譯的Ant技巧是你能夠安然過關
我能夠僅僅使用一組Java文件以及Ant工具
就能使一個版本同時編譯在
和
版本下面
別急
在我結識解決辦法之前
讓我先詳細的解釋一下問題的描述
可憐人的連接池(PS:Poor man
s connection pool
很有意思的一句話)
兩年前
我的公司需要一個連接池
但是又不肯出錢買一個
當時並沒有什麼免費的東東可以使用
所以我們自己寫了一個連接池
為了能更好的跟蹤在整個應用中連接的情況
我們寫了一個com
icentris
sql
ConnectionWrapper類
它實現了java
sql
Connection 接口以及其他的一些包裝類(實現了另外的一些的java
sql 接口)
這些包裝類僅僅是跟蹤我們應用中的數據庫使用
以及通過方法調用真正的數據庫資源
當J
SE
來的時候
我們自然而然的想到升級我們提供給客戶的應用
使這些應用的性能得到很多提升
當然
我們也需要保留
版本
因為有些客戶根本不需要升級到
我們氣惱的發現
如果我們不修改
我們的ConnectionWrapper 以及其他JDBC封裝類根本通不過J
SE
的編譯
為了文章的簡明
我通過使用ConnectionWrapper 這個類來演示我對所有其他不能夠通過J
SE
的類所使用的技術
如果我按照新的API標准
那麼我不得不添加幾個方法到ConnectionWrapper中去
接下來
個大問題擺在了面前
因為我的包裝類需要經歷方法調用
我將不得不調用在J
SE
sql類中並不存在的方法
因為一些新的方法涉及到一些新出現的類
我將不得不在編譯中面對那些在J
SE
中並不存在的類
反射提供了援助 一些代碼可以很方便的解釋第一個問題
但是我的ConnectionWrapper 封裝了java
sql
Connection
所有的我的例子
依賴於在構造方法中的變量 realConnection
private java
sql
Connection realConnection = null;
public ConnectionWrapper(java
sql
Connection connection) {
realConnection = connection;
}
為了看清楚我怎麼做到解決版本不兼容問題
讓我們仔細看一下setHoldability(int)(這個在J
SE
被聲明的新方法)
public void setHoldability(int holdability) throws SQLException {
realConnection
setHoldability( holdability );
}
很不幸
這個方法在J
SE
中顯然通不過編譯
這就陷入了
難的尴尬境地
為了解決這一情況
我假定setHoldability() 將只會在J
SE
下面被調用
所以我使用了反射機制來調用該方法
public void setHoldability(int holdability) throws SQLException {
Class[] argTypes = new Class[] { Integer
TYPE };
Object[] args = new Object[] {new Integer(holdability)};
callJava
Method(
setHoldability
realConnection
argTypes
args);
}
public static Object callJava
Method(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() 方法
因此能順利通過J
SE
的編譯
原理是我並不直接調用J
SE
中間java
sql
Connection並不存在的方法
而是轉為通過讓setHoldability調用callJava
Method這個通用方法來調用
然後在一個SQLException 裡封裝所有的異常
這樣就達到我預期的效果
現在所有的在J
SE
中新方法都工作的很好
在J
SE
的老版本下也能順利編譯而且工作正常
現在我來著手解決第二個問題
就是如何在應用中能夠找到一個方法能夠使用J
SE
中並不存在的新的類
Ant 是答案 在J
SE
中
java
sql
Connection 依賴於一個新的類java
sql
Savepoint
因為這個類在java
sql 包中
所以你不可能把它加入到J
SE
中去
Java不允許任何的第三方擴展包加入它的核心包(java
* 以及 javax
* )中去
因此挑戰來了
在J
SE
下調用這個新的java
sql
Savepoint 類
但同時需要代碼能夠在J
SE
下面得到編譯以及能夠運行
很簡單
不是嗎?所有回答
Yes
的人都會得到一個榛仁巧克力餅(PS:哈哈
我回答了
可是沒有
P)
至少現在我找到了答案
使問題變得很簡單了
首先我插入了下面一條有條件的import語句
// Comment_next_line_to_compile_with_Java_
import java
sql
Savepoint;
然後我找到了一個能夠在J
SE
下面注釋掉import的方法
非常簡單
使用如下Ant 語句就可以了
<replace>
<replacetoken>Comment_next_line_for_Java_
</replacetoken>
<replacevalue>Comment_next_line_for_Java_
//</replacevalue>
</replace>
這個Ant 的 replace 標簽 有好幾個標簽選項
在以後我給出的全部例子裡有很多
在這裡面最重要的是使用<replacevalue>來替換<replacetoken>
在XML裡面的意思是換行
在J
SE
下
沒什麼會發生
但是在J
SE
下面一個import聲明被注釋掉了
// Comment_next_line_to_compile_with_Java_
//import java
sql
Savepoint;
但是我在代碼中Savepoint仍在使用public Savepoint setSavepoint(String name) throws SQLException {
}
不過我只在J
SE
使用這些方法類
在J
SE
中只要能編譯就可以了
我發現只要我有一個我自己的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 J
SE
and J
SE
When compiled
* under J
SE
this class compiles as a placeholder instead of the
* missing java
sql
Savepoint (not in J
SE
)
When compiled
* under J
SE
this class is ignored and ConnectionWrapper uses the
* java
sql
Savepoint that is new in J
SE
*/
public class Savepoint {}
在J
SE
下我能夠正確的import java
sql
Savepoint類
而在J
SE
下面Ant注釋了這條import語句
因此這個Savepoint就被替換成了我這個包裡面寫的一個空的Savepoint類
所以我現在就能加入任何引用到Savepoint類的方法
同樣的在這些新方法中使用剛才所說的反射方法
// Comment_next_line_to_compile_with_Java_
import java
sql
Savepoint;
public Savepoint setSavepoint() throws SQLException {
Class[] argTypes = new Class[
];
Object[] args = new Object[
];<
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19513.html