概要
如果你要实现javaapi中的一个,那么可能是件比较痛苦的事情。你经常会需要实现许多交叉依赖的接口。对新特性的需求促成了升级现有的javaapi,这就造成了提供这些api的供应商对他们的相关实现不断的升级以维持相关功能。随着这些api的升级更改越来越频繁,api代码的不兼容使你不得不分别维护新旧版本的代码库。这直接到导致了你维护成本和难度的增加。本文演示了解决此问题的技术,揭示了如何仅使用一个代码库编译不同javaapi版本的代码。
现在非常多的api被加入到到java的标准库中,比如jdbc。这样做的好处是,java可选包在部署时不必被绑定到相关的部署应用中去。这些api由专门的专业开发小组实现,在实际的使用当中这些api变得越来越受欢迎,使用的深度及广度也在不断的增加。但是有时候对一些api升级会变得使一些类及方法不可用。开发小组宁愿让这些api包成为可选组件而不是作为java标准支持库的形式来发布。但是一旦加入标准库中的api包,就像是和用户签定了终生契约,想再成为可选包是不可能的。所以作为用户的你,可能会突然发现你一下子自己的代码库变成了不兼容的2个代码库,一个是使用新api的代码库,另一个是使用旧api的代码库。你可能会以为情况不像你想象的那样糟糕。我这里举一个简单的例子。j2se1.4中由于对jdbc中的一些api的升级使的java.sql.connection 不能同时被1.3 及 1.4 版本编译通过。你可能会遇到我这样的困境:我可能需要实现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类,它实现了java.sql.connection 接口以及其他的一些包装类(实现了另外的一些的java.sql 接口)。这些包装类仅仅是跟踪我们应用中的数据库使用,以及通过方法调用真正的数据库资源。
当j2se1.4来的时候,我们自然而然的想到升级我们提供给客户的应用,使这些应用的性能得到很多提升。当然,我们也需要保留1.3版本,因为有些客户根本不需要升级到1.4。我们气恼的发现,如果我们不修改,我们的connectionwrapper 以及其他jdbc封装类根本通不过j2se1.4的编译。
为了文章的简明,我通过使用connectionwrapper 这个类来演示我对所有其他不能够通过j2se1.4的类所使用的技术。如果我按照新的api标准,那么我不得不添加几个方法到connectionwrapper中去,接下来2个大问题摆在了面前:
1.因为我的包装类需要经历方法调用,我将不得不调用在j2se1.3 sql类中并不存在的方法。
2.因为一些新的方法涉及到一些新出现的类,我将不得不在编译中面对那些在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);
}
}
现在我有了setholdability() 方法,因此能顺利通过j2se1.4的编译。原理是我并不直接调用j2se1.3中间java.sql.connection并不存在的方法,
而是转为通过让setholdability调用calljava14method这个通用方法来调用,然后在一个sqlexception 里封装所有的异常。这样就达到我预期的效果。
现在所有的在j2se1.4中新方法都工作的很好,在j2se1.3的老版本下也能顺利编译而且工作正常。现在我来着手解决第二个问题。
就是如何在应用中能够找到一个方法能够使用j2se1.3中并不存在的新的类。
ant 是答案
在j2se1.4中,java.sql.connection 依赖于一个新的类java.sql.savepoint。因为这个类在java.sql 包中,所以你不可能把它加入到j2se1.3中去。java不允许任何的第三方扩展包加入它的核心包(java.* 以及 javax.* )中去。 因此挑战来了,在j2se1.4下调用这个新的java.sql.savepoint 类,但同时需要代码能够在j2se1.3下面得到编译以及能够运行。很简单,不是吗?所有回答"yes"的人都会得到一个榛仁巧克力饼(ps:哈哈,我回答了,可是没有:p)。至少现在我找到了答案,使问题变得很简单了。
首先我插入了下面一条有条件的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
</replacetoken>
<replacevalue>comment_next_line_for_java_1.3
//</replacevalue>
</replace>
这个ant 的 replace 标签 有好几个标签选项,在以后我给出的全部例子里有很多。在这里面最重要的是使用<replacevalue>来替换<replacetoken> 。
在xml里面的意思是换行。在j2se1.4下,没什么会发生, 但是在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中只要能编译就可以了。我发现只要我有一个我自己的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类。所以我现在就能加入任何引用到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];<
闽公网安备 35060202000074号