概要 如果你要实现JavaAPI中的一个那么可能是件比较痛苦的事情你经常会需要实现许多交叉依赖的接口对新特性的需求促成了升级现有的JavaAPI这就造成了提供这些API的供应商对他们的相关实现不断的升级以维持相关功能随着这些API的升级更改越来越频繁API代码的不兼容使你不得不分别维护新旧版本的代码库这直接到导致了你维护成本和难度的增加本文演示了解决此问题的技术揭示了如何仅使用一个代码库编译不同JavaAPI版本的代码 现在非常多的API被加入到到Java的标准库中比如JDBC这样做的好处是Java可选包在部署时不必被绑定到相关的部署应用中去这些API由专门的专业开发小组实现在实际的使用当中这些API变得越来越受欢迎使用的深度及广度也在不断的增加但是有时候对一些API升级会变得使一些类及方法不可用开发小组宁愿让这些API包成为可选组件而不是作为Java标准支持库的形式来发布但是一旦加入标准库中的API包就像是和用户签定了终生契约想再成为可选包是不可能的所以作为用户的你可能会突然发现你一下子自己的代码库变成了不兼容的个代码库一个是使用新API的代码库另一个是使用旧API的代码库你可能会以为情况不像你想象的那样糟糕我这里举一个简单的例子JSE中由于对JDBC中的一些API的升级使的javasqlConnection 不能同时被 及 版本编译通过你可能会遇到我这样的困境我可能需要实现javasqlConnection这个接口但是我的代码需要同时通过 及 得编译但是我不想同时维护个版本的代码库所以我开始寻找更好的解决方法 如果你依赖于javac来编译你的应用的话那么很不幸Java着名的一次编写到处运行(WORA)并不包括WOCA(一次编写到处编译^_^;) 不过别太沮丧编码的反射技巧以及编译的Ant技巧是你能够安然过关我能够仅仅使用一组Java文件以及Ant工具就能使一个版本同时编译在 和 版本下面别急在我结识解决办法之前让我先详细的解释一下问题的描述 可怜人的连接池(PS:Poor mans connection pool 很有意思的一句话) 两年前我的公司需要一个连接池但是又不肯出钱买一个当时并没有什么免费的东东可以使用所以我们自己写了一个连接池为了能更好的跟蹤在整个应用中连接的情况我们写了一个comicentrissqlConnectionWrapper类它实现了javasqlConnection 接口以及其他的一些包装类(实现了另外的一些的javasql 接口)这些包装类仅仅是跟蹤我们应用中的数据库使用以及通过方法调用真正的数据库资源 当JSE来的时候我们自然而然的想到升级我们提供给客户的应用使这些应用的性能得到很多提升当然我们也需要保留版本因为有些客户根本不需要升级到我们气恼的发现如果我们不修改我们的ConnectionWrapper 以及其他JDBC封装类根本通不过JSE的编译 为了文章的简明我通过使用ConnectionWrapper 这个类来演示我对所有其他不能够通过JSE的类所使用的技术如果我按照新的API标准那么我不得不添加几个方法到ConnectionWrapper中去接下来个大问题摆在了面前 因为我的包装类需要经历方法调用我将不得不调用在JSE sql类中并不存在的方法 因为一些新的方法涉及到一些新出现的类我将不得不在编译中面对那些在JSE中并不存在的类 反射提供了援助 一些代码可以很方便的解释第一个问题但是我的ConnectionWrapper 封装了javasqlConnection 所有的我的例子 依赖于在构造方法中的变量 realConnection private javasqlConnection realConnection = null; public ConnectionWrapper(javasqlConnection connection) { realConnection = connection; } 为了看清楚我怎么做到解决版本不兼容问题让我们仔细看一下setHoldability(int)(这个在JSE被声明的新方法) public void setHoldability(int holdability) throws SQLException { realConnectionsetHoldability( holdability ); } 很不幸这个方法在JSE中显然通不过编译这就陷入了难的尴尬境地为了解决这一情况我假定setHoldability() 将只会在JSE 下面被调用所以我使用了反射机制来调用该方法 public void setHoldability(int holdability) throws SQLException { Class[] argTypes = new Class[] { IntegerTYPE }; Object[] args = new Object[] {new Integer(holdability)}; callJavaMethod(setHoldability realConnection argTypes args); } public static Object callJavaMethod(String methodName Object instance Class[] argTypes Object[] args) throws SQLException { try { Method method = instancegetClass()getMethod(methodName argTypes); return methodinvoke(instance args ); } catch (NoSuchMethodException e) { eprintStackTrace(); throw new SQLException(Error Invoking method ( + methodName + ): + e); } catch (IllegalAccessException e) { eprintStackTrace(); throw new SQLException(Error Invoking method ( + methodName + ): + e); } catch (InvocationTargetException e) { eprintStackTrace(); throw new SQLException(Error Invoking method ( + methodName + ): + e); } } 现在我有了setHoldability() 方法因此能顺利通过JSE的编译原理是我并不直接调用JSE中间javasqlConnection并不存在的方法 而是转为通过让setHoldability调用callJavaMethod这个通用方法来调用然后在一个SQLException 里封装所有的异常这样就达到我预期的效果 现在所有的在JSE中新方法都工作的很好在JSE的老版本下也能顺利编译而且工作正常现在我来着手解决第二个问题 就是如何在应用中能够找到一个方法能够使用JSE中并不存在的新的类 Ant 是答案 在JSE中javasqlConnection 依赖于一个新的类javasqlSavepoint因为这个类在javasql 包中所以你不可能把它加入到JSE中去Java不允许任何的第三方扩展包加入它的核心包(java* 以及 javax* )中去 因此挑战来了在JSE下调用这个新的javasqlSavepoint 类但同时需要代码能够在JSE下面得到编译以及能够运行很简单不是吗?所有回答Yes的人都会得到一个榛仁巧克力饼(PS:哈哈我回答了可是没有P)至少现在我找到了答案使问题变得很简单了 首先我插入了下面一条有条件的import语句 // Comment_next_line_to_compile_with_Java_ import javasqlSavepoint; 然后我找到了一个能够在JSE下面注释掉import的方法非常简单使用如下Ant 语句就可以了 <replace> <replacetoken>Comment_next_line_for_Java_ </replacetoken> <replacevalue>Comment_next_line_for_Java_ //</replacevalue> </replace> 这个Ant 的 replace 标签 有好几个标签选项在以后我给出的全部例子里有很多在这里面最重要的是使用<replacevalue>来替换<replacetoken> 在XML里面的意思是换行在JSE下没什么会发生 但是在JSE下面一个import声明被注释掉了 // Comment_next_line_to_compile_with_Java_ //import javasqlSavepoint; 但是我在代码中Savepoint仍在使用public Savepoint setSavepoint(String name) throws SQLException { }不过我只在JSE使用这些方法类在JSE中只要能编译就可以了我发现只要我有一个我自己的Savepoint 类在我的包中我的代码就能够通过编译而且不用任何的import包但是我又要同时在这条import 语句不被注释的同时我自己的Savepoint类被忽略掉因此我造了一个空的comicentrissqlSavepoint类这个可能(除了JavaDoc)是最短的有效类 package comicentrissql; /** Dummy class to allow ConnectionWrapper to implement javasqlConnection * and still compile under JSE and JSE When compiled * under JSE this class compiles as a placeholder instead of the * missing javasqlSavepoint (not in JSE ) When compiled * under JSE this class is ignored and ConnectionWrapper uses the * javasqlSavepoint that is new in JSE */ public class Savepoint {} 在JSE下我能够正确的import javasqlSavepoint类而在JSE下面Ant注释了这条import语句因此这个Savepoint就被替换成了我这个包里面写的一个空的Savepoint类所以我现在就能加入任何引用到Savepoint类的方法同样的在这些新方法中使用刚才所说的反射方法 // Comment_next_line_to_compile_with_Java_ import javasqlSavepoint; public Savepoint setSavepoint() throws SQLException { Class[] argTypes = new Class[]; Object[] args = new Object[];< |