Java自定义类加载器实现类隔离详解

 更新时间:2023年03月02日 09:05:59   作者:kamier  
由于每种组件的不同版本所依赖的jar包不同,我们可以借鉴tomcat的实现方式,通过自定义类加载器打破双亲委派机制来实现类隔离,从而达到操作多组件多版本的目的。本文就来和大家详细聊聊实现方法

一、背景

某服务需要连接操作多种组件(每种组件可能有多个版本),如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;
    }
}

4.打成jar包

5.将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, 参数);

获取到结果并返回

三、总结

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

到此这篇关于Java自定义类加载器实现类隔离详解的文章就介绍到这了,更多相关Java类加载器实现类隔离内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用filebeat收集并解析springboot日志过程示例

    使用filebeat收集并解析springboot日志过程示例

    这篇文章主要为大家介绍了使用filebeat收集并解析springboot日志实现过程示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 详解Struts2标签遍历

    详解Struts2标签遍历

    这篇文章主要介绍了Struts2标签遍历,以及相关的用法示例,需要的朋友可以参考下。
    2017-09-09
  • 详解idea中web.xml默认版本问题解决

    详解idea中web.xml默认版本问题解决

    这篇文章主要介绍了详解idea中web.xml默认版本问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • java编程无向图结构的存储及DFS操作代码详解

    java编程无向图结构的存储及DFS操作代码详解

    这篇文章主要介绍了java编程无向图结构的存储及DFS操作代码详解,具有一定借鉴价值,需要的朋友可以了解下。
    2017-12-12
  • Java 交换两个变量的数值实现方法

    Java 交换两个变量的数值实现方法

    下面小编就为大家带来一篇Java 交换两个变量的数值实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-07-07
  • Spring Cloud Gateway内置的断言和过滤器作用说明

    Spring Cloud Gateway内置的断言和过滤器作用说明

    这篇文章主要介绍了Spring Cloud Gateway内置的断言和过滤器作用说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Java设计模式之java访问者模式详解

    Java设计模式之java访问者模式详解

    这篇文章主要介绍了JAVA设计模式之访问者模式,简单说明了访问者模式的原理,并结合实例分析了java访问者模式的定义与用法,需要的朋友可以参考下
    2021-09-09
  • Java局部内部类原理与用法实例分析

    Java局部内部类原理与用法实例分析

    这篇文章主要介绍了Java局部内部类原理与用法,结合实例形式分析了Java局部内部类功能、用法及相关操作注意事项,需要的朋友可以参考下
    2019-09-09
  • Spring MVC内置过滤器功能示例详解

    Spring MVC内置过滤器功能示例详解

    这篇文章主要为大家介绍了Spring MVC内置过滤器使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • 聊聊Spring Cloud Cli 初体验

    聊聊Spring Cloud Cli 初体验

    这篇文章主要介绍了聊聊Spring Cloud Cli 初体验,SpringBoot CLI 是spring Boot项目的脚手架工具。非常具有实用价值,需要的朋友可以参考下
    2018-04-04

最新评论