一、背景
某服务须要连贯操作多种组件(每种组件可能有多个版本
),如kafka、mongodb、es、mysql等等,并且后续须要适配更多的组件。
次要难点:连贯操作多组件多版本,且同种组件的不同版本所依赖的jar包可能不一样,操作源码也可能产生扭转,我的项目无奈间接依赖jar包,会产生类抵触
二、解决思路
因为每种组件的不同版本所依赖的jar包不同,咱们能够借鉴tomcat的实现形式,通过自定义类加载器突破双亲委派机制来实现类隔离,从而达到操作多组件多版本的目标。
2.1 创立依赖所在目录
针对每一种组件咱们创立一个目录,比方/data/kafka、/data/mongodb、/data/es等,且每种组件的不同版本创立对应的子目录,比方/data/kafka/0.10、/data/kafka/0.11,目录构造如下
| ----/data| --------/kafka| ------------/0.10| ------------/0.11| --------/mysql| ------------/5.7| ------------/8.0| ...
把每种组件不同版本对应的依赖包放在各个子目录上面。
2.2 定义操作接口
在common公共模块中定义一个接口AbstractOperator,该接口定义一些通用办法,如下:
public interface Operator { /** * 测试连贯 * @param connectionInfo * @return */ boolean testConnection(String connectionInfo); /** * 获取组件版本 * @return */ String getVersion(String connectionInfo);}
再定义各种组件的接口,如KafkaOperator、MysqlOperator等,使其继承该通用接口。组件接口外部蕴含一些组件本身的操作,如KafkaOperator中定义了getTopics、createTopic、deleteTopic等办法。代码如下:
public interface KafkaOperator extends Operator{ /** * 获取topic列表 * @param connectionInfo * @return */ List<String> getTopics(String connectionInfo); /** * 创立topic * @param connectionInfo * @param topic * @return */ boolean createTopic(String connectionInfo, String topic); /** * 删除topic * @param connectionInfo * @param topic * @return */ boolean deleteTopic(String connectionInfo, String topic);}
2.3 编写并构建业务包
大抵步骤如下:
- 针对每种组件的不同版本,能够在我的项目下新建一个模块,该模块依赖common公共模块
- 创立入口类com.kamier.Entry(所有组件的不同版本的入口类的全限定名对立为com.kamier.Entry),并实现对应的组件接口,比方Kafka的0.10版本,那么就实现KafkaOperator接口。
- 编写业务逻辑代码
public class Entry implements KafkaOperator { @Override public List<String> getTopics(String connectionInfo) { return null; } @Override public boolean createTopic(String connectionInfo, String topic) { return false; } @Override public boolean deleteTopic(String connectionInfo, String topic) { return false; } @Override public boolean testConnection(String connectionInfo) { return false; } @Override public String getVersion(String connectionInfo) { return null; }}
- 打成jar包
- 将jar包放在对应的目录下,与依赖包同级,如/data/kafka/0.10
2.4 自定义类加载器
通过后面的筹备工作,组件的每个版本的目录下都有了相应的依赖包和业务包。
开始编写一个自定义类加载器继承URLClassLoader,重写loadClass办法,优先加载以后类加载器门路下的class来突破双亲委派模式,代码如下
public static class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] urls) { super(urls); } public Class<?> loadClass(String name) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 先查看以后类加载器是否曾经装载该类 Class<?> c = findLoadedClass(name); if (c == null) { try { // 在以后类加载器的门路下查找 c = findClass(name); } catch (ClassNotFoundException e) { // 阐明在以后类加载器的门路下没有找到 } if (c == null) { // 走双亲委派机制 if (getParent() != null) { c = getParent().loadClass(name); } } } return c; } } }
针对每种组件的不同版本,咱们创立与其对应的自定义类加载器,并将该版本对应目录下的所有jar包(包含依赖包和业务包)的URL传入。
2.5 主流程步骤
步骤如下:
- 当咱们从页面上接管到一个获取Kafka(版本为0.10)topic列表的申请时,先判断是否曾经初始化过Kafka(0.10版本)的类加载器,如果还未初始化,则进行类加载器的初始化。
URL[] urls = null;File dir = new File("/data/kafka/0.10");if (dir.isDirectory()) { File[] files = dir.listFiles(); urls = new URL[files.length]; for (int i = 0; i < files.length; i++) { urls[i] = files[i].toURL(); }}MyClassLoader contextClassLoader = new MyClassLoader(urls);
- 通过类加载器加载入口类com.kamier.Entry并实例化,通过反射调用对应的办法(组件与其对应的办法列表能够对立保护在数据库中)。
Class loadClass = contextClassLoader.loadClass("com.kamier.Entry");Object entry = loadClass.newInstance();Method method = loadClass.getDeclaredMethod("getTopics");List<String> a = (List) method.invoke(entry, 参数);
- 获取到后果并返回
三、总结
至此整个实现步骤就完结了,咱们通过自定义类加载器的形式来实现类隔离,从而达到操作多组件多版本的目标。