地球上的程序员都知道,当自己发布一个新版本库的时候,一定要注意向上下左右前后东南西北中发白各个方向的兼容性,尽可能做到最大程度的兼容。
然而还有另外一种情况 -- 如果自己依赖的库,某天升级后新旧版本间有少量不兼容,这时该怎么办?最直接的办法是也随着它拆分为两个版本,适用于绝大部分情况,但是也有额外的维护负担等缺点;此外如果这个第三方库并不是那么重要,或者其不兼容的地方不是特别多的话,那么为了它而拆分就没必要了。
记录下这个有两个目的,一个是分享一下实际项目中遇到这个兼容性问题是如何解决的;另外就是看看朋友们有没有其它的思路可以再开拓一下。
下面就用简化过的代码来说明一下问题以及解决思路:
(1)第三方库的第一个版本中,有一个业务函数可供调用:
package org.otherlib;
public class Otherlib{
public static String getLibVersion(){
return "Otherlib V1.0 (use static method)";
}
// others... ...
}
(2)自己的库中,用到了这个函数
package com.iteye.goonfly;
import org.otherlib.*;
public class Ourlib{
public static void main(String[] args){
System.out.println("==== Using lib : " + Otherlib.getLibVersion() + " ====");
}
}
编译运行如下:
$javac -d ./classes/ otherlib1/Otherlib.java
$javac -cp ./classes/ -d ./classes/ oursrc/Ourlib.java
$java -cp ./classes/ com.iteye.goonfly.Ourlib
==== Using lib : Otherlib V1.0 (use static method) ====
(3)第三方库的第二个版本,删掉了这个函数,改用了静态常量字符串
package org.otherlib;
public class Otherlib{
public static final String LIB_VERSION =
"Otherlib V2.0 (use static String)";
// others... ...
}
这种情况下,可以逐个修改自己的库里面的调用处代码,与之新版本对应:
package com.iteye.goonfly;
import org.otherlib.*;
public class Ourlib2{
public static void main(String[] args){
//System.out.println("Using lib : " + Otherlib.getLibVersion());
System.out.println("==== Using lib : " + Otherlib.LIB_VERSION + " ====");
}
}
但是问题则是,自己的库与第三方库新版本可以配合适用,和旧版本则不行了,喜新厌旧,恐怕要出大事。。。
由于兼容性的问题不大(不是逻辑上的架构上的或者有大量的接口变更等),所以除了也随着发布两个版本外,还可以采用“反射机制+代理思想,用一个自己的库,可以同时兼容两个版本的第三方库,代码如下:
(4)自己库的新版本,不再直接调用有变更的地方,而是调用代理类,差异的部分,让代理通过反射处理
package com.iteye.goonfly;
//import org.otherlib.*;
public class Ourlib3{
public static void main(String[] args){
//System.out.println("Using lib : " + Otherlib.getLibVersion());
System.out.println("==== Using lib : " + OtherlibProxy.getLibVersion() + " ====");
}
}
(5)新增的代理类
package com.iteye.goonfly;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class OtherlibProxy{
public static String getLibVersion(){
Class clazz = null;
try {
clazz = Class.forName("org.otherlib.Otherlib");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(null == clazz){
return "!!!!Class org.otherlib.Otherlib NOT found!!!!";
}
try {
// if it has getLibVersionMethod() method
Method getLibVersionMethod = clazz.getMethod("getLibVersion", new Class[0]);
return (String)getLibVersionMethod.invoke(null, new Object[0]);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
try {
// or else it has the static string field
Field field = clazz.getField("LIB_VERSION");
return (String)field.get(clazz);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "!!!!NO getLibVersion() method OR LIB_VERSION string found!!!!";
}
}
(6)测试验证一下
先试试旧版本
$javac -d ./classes/ otherlib1/Otherlib.java
$javac -cp ./classes/ -d ./classes/ oursrc/OtherlibProxy.java
$javac -cp ./classes/ -d ./classes/ oursrc/Ourlib3.java
$java -cp ./classes/ com.iteye.goonfly.Ourlib3
==== Using lib : Otherlib V1.0 (use static method) ====
再试试新版本
$javac -d ./classes/ otherlib2/Otherlib.java
$javac -cp ./classes/ -d ./classes/ oursrc/OtherlibProxy.java
$javac -cp ./classes/ -d ./classes/ oursrc/Ourlib3.java
$java -cp ./classes/ com.iteye.goonfly.Ourlib3
java.lang.NoSuchMethodException: org.otherlib.Otherlib.getLibVersion()
at java.lang.Class.getMethod(Class.java:1622)
at com.iteye.goonfly.OtherlibProxy.getLibVersion(OtherlibProxy.java:22)
at com.iteye.goonfly.Ourlib3.main(Ourlib3.java:8)
==== Using lib : Otherlib V2.0 (use static String) ====
可以看到,抛出了异常,并且成功调用到了新版本库,测试一切正常后,捕获到异常不输出信息即可,将来的某一天,如果新版本把旧版本彻底的替换淘汰了,那就代理类里面修改一下,是不是design pattern相关的味道出来一点点了^_^