乐趣区

关于java:JAVA自定义类加载器实现类隔离

一、背景

某服务须要 连贯操作多种组件 每种组件可能有多个版本),如 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 编写并构建业务包

大抵步骤如下:

  1. 针对每种组件的不同版本,能够在我的项目下新建一个模块,该模块依赖 common 公共模块
  2. 创立入口类 com.kamier.Entry(所有组件的不同版本的入口类的全限定名对立为 com.kamier.Entry),并实现对应的组件接口,比方 Kafka 的 0.10 版本,那么就实现 KafkaOperator 接口。
  3. 编写业务逻辑代码
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;}
}
  1. 打成 jar 包
  2. 将 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 主流程步骤

步骤如下:

  1. 当咱们从页面上接管到一个获取 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);
  1. 通过类加载器加载入口类 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, 参数);
  1. 获取到后果并返回

三、总结

至此整个实现步骤就完结了,咱们通过自定义类加载器的形式来实现类隔离,从而达到操作多组件多版本的目标。

退出移动版