springboot自定义ClassLoader实现同一个jar支持多版本的使用场景

  • 背景

    最近业务提出一个业务场景:系统目前支持hive3.1.0版本的数据源适配,但是有个别部门使用的数据源是hive2.1.1版本,但是hive3.1.0版本的驱动无法支持连接hive2.1.1的hive数据源;这就提出了新的目标:在同一个系统既要支持hive3.1.0版本同时又要支持hive2.1.1版本的数据源功能;

  • 思路分析

    1.java的执行都是通过类加载运行的,其加载运行过程如下:
    加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
    (不在详解,需要了解的百度一下吧),其多版本的兼容运行也离不开多版本的jar加载其运行:对于不同的hive版本分别classLoader不同的支持版本的lib jar就可以实现该目标;
    2.java类加载器有引导类(Launcher)、扩展类(ExtClassLoader)、应用程序类加载器(AppClassLoader)以及支持自定义加载器,其JVM类的加载类有一个双亲委派的机制:加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。正是该机制导致了系统无法支持同一个jar的多版本加载;解决该问题就只能自定义加载类,打破双亲委派机制,通过指定的lib jar路径去加载需要class 以达到支持不同版本功能;

    经过如上的理论分析,实现方案有了理论支持,下面就直接开始代码实现上面的思路.

  • 代码实现和验证

    1.demo环境和前提准备:

    springboot 2.5.6mysql-connector-java 5.1.35hive 3.1.X需要的依赖包如:/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/hive2.1.1需要的依赖包路径如:/Users/lifangyu/soft/driver-lib/hive-jdbc-2.1.1/hive2.1.1 版本数据源信息url:jdbc:hive2://127.0.0.1:10001/demouser:testpwd:testhive3.1.X 版本数据源连接信息url:jdbc:hive2://127.0.0.1:2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2user:testpwd:test
2.自定义加载类实现,打破双亲委派机制,通过自定义指定的jar 文件路径加载类JarClassLoader,源码如下:
package com.bigdata.myClassLoader;import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;/*** description 自定义加载类:打破双全委派机制** @author Cyber* <p> Created By 2022/11/22* @version 1.0*/
public class JarClassLoader extends URLClassLoader {private static ThreadLocal<URL[]> threadLocal = new ThreadLocal<>();private URL[] allUrl;public JarClassLoader(String[] paths) {this(paths, JarClassLoader.class.getClassLoader());}public JarClassLoader(String[] paths, ClassLoader parent) {super(getURLs(paths), parent);// 当前线程防止重复读取文件信息,可使用其他缓存代替allUrl = threadLocal.get();}public JarClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent);}/*** description 通过文件目录获取目录下所有的jar全路径信息** @param paths 文件路径* @return java.net.URL[]* @author Cyber* <p> Created by 2022/11/22*/private static URL[] getURLs(String[] paths) {if (null == paths || 0 == paths.length) {throw new RuntimeException("jar包路径不能为空.");}List<String> dirs = new ArrayList<String>();for (String path : paths) {dirs.add(path);JarClassLoader.collectDirs(path, dirs);}List<URL> urls = new ArrayList<URL>();for (String path : dirs) {urls.addAll(doGetURLs(path));}URL[] threadLocalurls = urls.toArray(new URL[0]);threadLocal.set(threadLocalurls);return threadLocalurls;}/*** description 递归获取文件目录下的根目录** @param path      文件路径* @param collector 根目录* @return void* @author Cyber* <p> Created by 2022/11/22*/private static void collectDirs(String path, List<String> collector) {if (null == path || "".equalsIgnoreCase(path)) {return;}File current = new File(path);if (!current.exists() || !current.isDirectory()) {return;}for (File child : current.listFiles()) {if (!child.isDirectory()) {continue;}collector.add(child.getAbsolutePath());collectDirs(child.getAbsolutePath(), collector);}}private static List<URL> doGetURLs(final String path) {if (null == path || "".equalsIgnoreCase(path)) {throw new RuntimeException("jar包路径不能为空.");}File jarPath = new File(path);if (!jarPath.exists() || !jarPath.isDirectory()) {throw new RuntimeException("jar包路径必须存在且为目录.");}FileFilter jarFilter = new FileFilter() {/*** description  判断是否是jar文件* @param pathname jar 全路径文件* @return boolean* @author Cyber* <p> Created by 2022/11/22*/@Overridepublic boolean accept(File pathname) {return pathname.getName().endsWith(".jar");}};File[] allJars = new File(path).listFiles(jarFilter);List<URL> jarURLs = new ArrayList<URL>(allJars.length);for (int i = 0; i < allJars.length; i++) {try {jarURLs.add(allJars[i].toURI().toURL());} catch (Exception e) {throw new RuntimeException("系统加载jar包出错", e);}}return jarURLs;}/*** description 重新loadClass加载过程,打破双亲委派机制,采用逆向双亲委派** @param className 加载的类名* @return java.lang.Class<?>* @author Cyber* <p> Created by 2022/11/22*/@Overridepublic Class<?> loadClass(String className) throws ClassNotFoundException {if (allUrl != null) {String classPath = className.replace(".", "/");classPath = classPath.concat(".class");for (URL url : allUrl) {byte[] data = null;ByteArrayOutputStream baos = new ByteArrayOutputStream();InputStream is = null;try {File file = new File(url.toURI());if (file != null && file.exists()) {JarFile jarFile = new JarFile(file);if (jarFile != null) {JarEntry jarEntry = jarFile.getJarEntry(classPath);if (jarEntry != null) {is = jarFile.getInputStream(jarEntry);int c = 0;while (-1 != (c = is.read())) {baos.write(c);}data = baos.toByteArray();System.out.println("********找到classPath=" + classPath + "的jar=" + url.toURI().getPath() + "*******");return this.defineClass(className, data, 0, data.length);}}}} catch (Exception e) {e.printStackTrace();} finally {try {if (is != null) {is.close();}baos.close();} catch (IOException e) {e.printStackTrace();}}}}// 未找到的情况下通过父类加载器加载return super.loadClass(className);}
}
3.当前线程ContextClassLoader的装载和卸载处理
package com.bigdata.myClassLoader;/*** description 自定义classloader加载器当前线程的ContextClassLoader处理** @author Cyber* <p> Created By 2022/11/22* @version 1.0*/
public class JarClassLoaderSwapper {private ClassLoader storeClassLoader = null;private JarClassLoaderSwapper() {}public static JarClassLoaderSwapper newCurrentThreadClassLoaderSwapper() {return new JarClassLoaderSwapper();}/*** description 保存当前classLoader,并将当前线程的classLoader设置为所给classLoader** @param classLoader* @return java.lang.ClassLoader* @author Cyber* <p> Created by 2022/11/22*/public ClassLoader setCurrentThreadClassLoader(ClassLoader classLoader) {this.storeClassLoader = Thread.currentThread().getContextClassLoader();Thread.currentThread().setContextClassLoader(classLoader);return this.storeClassLoader;}/*** description 将当前线程的类加载器设置为保存的类加载** @param* @return java.lang.ClassLoader* @author Cyber* <p> Created by 2022/11/22*/public ClassLoader restoreCurrentThreadClassLoader() {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();Thread.currentThread().setContextClassLoader(this.storeClassLoader);return classLoader;}
}
  • 结果验证

    测试类:JarClassLoaderHiveTest

package com.bigdata.hive;import com.bigdata.myClassLoader.JarClassLoader;
import com.bigdata.myClassLoader.JarClassLoaderSwapper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.reflect.InvocationTargetException;
import java.sql.*;
import java.util.Properties;/*** description hive多版本自定义加载类测试** @author Cyber* <p> Created By 2022/11/22* @version 1.0*/
public class JarClassLoaderHiveTest {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, SQLException {String jarUrl2 = "/Users/lifangyu/soft/driver-lib/hive-jdbc-2.1.1/"; //自己定义的测试jar包,不同版本打印内容不同/*** description lib/目录下的日子文件删除以防冲突* log4j-1.2-api-2.10.0.jar* log4j-api-2.10.0.jar* log4j-core-2.10.0.jar* log4j-slf4j-impl-2.10.0.jar* log4j-web-2.10.0.jar*/String url = "jdbc:hive2://127.0.0.1:10001/ty";String user = "test";String pwd = "test";// String sql = "show databases";// String sql = "show tables";String sql = "select * from user_level_demo1 limit 10";testHiveJdbc(jarUrl2, url, user, pwd, sql);String jarUrl3 = "/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/";//自己定义的测试jar包,不同版本打印内容不同/*** description lib/目录下的日子文件删除以防冲突* log4j-1.2-api-2.10.0.jar* log4j-api-2.10.0.jar* log4j-core-2.10.0.jar* log4j-slf4j-impl-2.10.0.jar* log4j-web-2.10.0.jar*/url = "jdbc:hive2://127.0.0.1:2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2";user = "test";pwd = "test";String sql3 = "SELECT x.* FROM dc_dwa.dwa_d_bd_blend x where x.pro_id = 10 and x.contact_no='13009502690'";testHiveJdbc(jarUrl3, url, user, pwd, sql3);}private static void testHiveJdbc(String jarUrl, String url, String user, String pwd, String sql) throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException {long start = System.currentTimeMillis();JarClassLoader jarLoader = new JarClassLoader(new String[]{jarUrl});JarClassLoaderSwapper classLoaderSwapper = JarClassLoaderSwapper.newCurrentThreadClassLoaderSwapper();classLoaderSwapper.setCurrentThreadClassLoader(jarLoader);Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass("org.apache.hive.jdbc.HiveDriver");classLoaderSwapper.restoreCurrentThreadClassLoader();Driver driver = (Driver) aClass.newInstance();Properties properties = new Properties();properties.put("user", user);properties.put("password", pwd);Connection conn = driver.connect(url, properties);PreparedStatement pstmt = (PreparedStatement) conn.prepareStatement(sql);ResultSet rs = pstmt.executeQuery();int col = rs.getMetaData().getColumnCount();//列数System.out.println("============================:" + jarUrl);while (rs.next()) {//一行一行输出for (int i = 1; i <= col; i++) {System.out.print(rs.getString(i) + "\t");//输出if ((i == 2) && (rs.getString(i).length() < 8)) {//输出制表符System.out.print("\t");}}System.out.println("");}System.out.println("============================耗时:" + (System.currentTimeMillis() - start) + "ms");}
}

查看测试输出结果如下:

********找到classPath=com/google/common/base/Preconditions.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-2.1.1/guava-14.0.1.jar*******
********找到classPath=org/apache/hive/service/cli/ColumnBasedSet$1.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-2.1.1/hive-service-2.1.1.jar*******
********找到classPath=org/apache/hadoop/hive/serde2/thrift/ColumnBuffer$1.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-2.1.1/hive-serde-2.1.1.jar*******
********找到classPath=org/apache/hive/jdbc/HiveBaseResultSet$1.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-2.1.1/hive-jdbc-2.1.1.jar*******
1014040324171307    18547200338 V0150200    0472    null    40AAAAAA    1   0   1   202205  null    202203  202205  010 1
1014040924175631    18547304560 V0150300    0473    null    50AAAAAA    3   0   1   202205  null    202203  202205  010 1
1014041524181440    18647010211 V0152302    0470    null    40AAAAAA    2   0   1   202205  null    202203  202205  010 1
1014042424194176    18547225591 V0150200    0472    null    40AAAAAA    2   0   1   202205  null    202203  202205  010 1
1014042524195969    18604775277 V0152700    0477    null    40AAAAAA    2   0   1   202205  null    202112  202205  010 1
1014042624197401    18648538891 V0152301    0475    null    40AAAAAA    2   0   1   202205  null    202203  202205  010 1
1014042924201464    18647164248 V0150400    0476    null    40AAAAAA    1   0   1   202205  null    202111  202205  010 1
1014042924203019    18604717645 V0150100    0471    null    50AAAAAA    4   0   1   202205  null    202203  202205  010 1
1014043024203640    18547030504 V0152302    0470    null    40AAAAAA    2   0   1   202205  null    202203  202205  010 1
1014043024203736    18647896194 V0152800    0478    null    40AAAAAA    2   0   1   202205  null    202205  202205  010 1
============================耗时:7822ms
********找到classPath=org/apache/hive/jdbc/HiveDriver.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/hive-jdbc-3.1.2.jar*******
********找到classPath=org/apache/hive/jdbc/ZooKeeperHiveClientException.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/hive-jdbc-3.1.2.jar*******
********找到classPath=org/apache/hive/jdbc/HiveConnection.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/hive-jdbc-3.1.2.jar*******
********找到classPath=org/apache/thrift/TException.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/libthrift-0.9.3.jar*******
********找到classPath=org/apache/http/client/HttpClient.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/httpclient-4.5.2.jar*******
********找到classPath=org/apache/thrift/transport/TTransport.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/libthrift-0.9.3.jar*******
... ...
********找到classPath=org/apache/thrift/TBaseHelper$NestedStructureComparator.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/libthrift-0.9.3.jar*******
********找到classPath=org/apache/hive/service/cli/ColumnBasedSet$1.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/hive-service-3.1.2.jar*******
********找到classPath=org/apache/hadoop/hive/serde2/thrift/ColumnBuffer$1.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/hive-serde-3.1.2.jar*******
********找到classPath=org/apache/hive/jdbc/HiveBaseResultSet$1.class的jar=/Users/lifangyu/soft/driver-lib/hive-jdbc-3.1.2/hive-jdbc-3.1.2.jar*******
10  13009502690 40AAAAAA    047103676479    202211  15
10  13009502690 40AAAAAA    047103676479    202209  22
10  13009502690 40AAAAAA    047103676479    202209  24
============================耗时:59360ms

通过以上测试即可看到hive2.1.1版本和hive3.1.X版本运行分别使用的是各自相关依赖包加载运行,没有依赖冲突;

  • 总结思考

    以上只是这一种场景的思考解决,在实际的项目中,有关该类问题jar包冲突导致的种种问题都可以使用该思想去解决,当然以上只是抛砖引玉的解决思路,在生产使用还建议结合项目实际情况结合适配器等设计模式进行封装和优化以完成生产力使用;

springboot自定义ClassLoader实现同一个jar支持多版本的使用场景【附源码】相关推荐

  1. SpringBoot+RabbitMQ ,保证消息100%投递成功并被消费(附源码)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:rrd.me/f2cxz 一.先扔一张图 说明: 本文涵盖了 ...

  2. 基于Vue+SpringBoot智慧校园疫情防控系统(PC端、手机端)--附源码

    介绍 智慧校园疫情防控系统--PC 手机端 多端并行 项目源码下载:https://download.csdn.net/download/DeepLearning_/87340321 软件架构 手机端 ...

  3. 基于CNTK实现自定义类库及使用方式(MNIST为例)【附源码】

    文章目录 前言 一.工程目录结构 二.类库的实现 1.卷积层Conv2D的构建 2.标准化BatchNormalization的构建 3.激活函数Relu的构建 4.呈现形式 三.类库的调用 1.读取 ...

  4. Android 开发中原始音频的录播和和自定义音频控制条的讲解及实战(超详细 附源码)

    需要源码请点赞关注收藏后评论区留下QQ~~~ 一.原始音频的录播 语音通话功能要求实时传输,如果使用MediaRecorder与MediaPlayer组合,那么只能整句话都录完并编码好了才能传给对方去 ...

  5. Springboot+mysql+基于VUE框架的商城综合项目设计与实现 毕业设计-附源码111612

    基于VUE框架的商城综合项目设计与实现 摘 要 随着科学技术的飞速发展,社会的方方面面.各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,商城综合项目当然也不能排除在外.商城综合项目 ...

  6. springboot基于web的传染病信息管理系统的设计与实现毕业设计-附源码221124

    摘 要 传染病报告是传染病防控工作的重要内容,医疗机构及其执行职务的人员开展传染病疫情报告工作是中华人民共和国传染病防治法>第三十条赋予的法定职责.传染病上报系统的不断完善提高了传染病暴发的早期 ...

  7. springboot基于web模式的师资管理系统的设计与实现 毕业设计-附源码040928

    springboot师资管理系统设计与实现 摘 要 随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理.在现实运用中,应用软件的工作 ...

  8. springboot+mysql+建筑造价师资格考试应试网站设计与实现 毕业设计-附源码260839

    Springboot建筑造价师资格考试应试网站 摘  要 如何合理确定和有效控制工程投资,是工程项目建设的一大难题,如何使建筑工程造价管理与社会生产水平相适应,是建筑工程造价管理中需要解决的问题,只有 ...

  9. springboot+基于web的传染病信息管理系统的设计与实现 毕业设计-附源码221124

    基于web的传染病信息管理系统的设计与实现 摘 要 传染病报告是传染病防控工作的重要内容,医疗机构及其执行职务的人员开展传染病疫情报告工作是中华人民共和国传染病防治法>第三十条赋予的法定职责.传 ...

最新文章

  1. python程序结构有哪几种_Python数据结构与算法(几种排序)小结
  2. NG RouteReuseStrategy(路由复用策略)
  3. 沃尔玛痛失世界最大零售商 电商凶猛!
  4. Spring,FetchType.LAZY和FetchType.EAGER什么区别?
  5. 错误信息: 集合已修改;可能无法执行枚举操作。
  6. DRBD安装编译后: modprobe drdb FATAL: Module drdb not found
  7. paip.提升用户体验---显示密码控件ShowPwdController
  8. FTP搜索引擎的设计与实现
  9. 索尼计算机bios正确设置,索尼bios设置图解教程
  10. java异常以及处理
  11. 【散文诗】STM32时钟框图
  12. thinkphp6如何部署在虚拟主机上?
  13. php调用ua_PHP判断判断UA:检测客户端是手机或电脑
  14. 赛迪智库联合百分点在数博会上发布消费型数字经济报告
  15. 扬帆际海:个人如何做跨境电商
  16. linux账号安全管理,保证Linux系统安全——帐号管理
  17. 基于STC89C51的交通信号灯控制程序
  18. 在职人员缴纳的社保跟灵活就业人员缴纳的社保到底有什么不同?
  19. Ubuntu Server 个人影音服务器,实现NAS、远程下载、私人云盘等
  20. T4牛半仙的魔塔(增强版)

热门文章

  1. 30最棒的响应式css前端框架
  2. python电影系统管理-Python 爬取电影网站的信息【如有重复请管理删帖】
  3. gspca 摄像头驱动的移植(ZC3XX)
  4. 听说你们好奇我技术文章里的动图是怎么做的 ?
  5. 这一篇和大家聊聊Hadoop
  6. PaddlePaddle第二周学习笔记
  7. 分页第一页用0还是1_洗脸用冷水还是热水好?这些错误只要犯1个,你的脸就白洗了!...
  8. 计算机用户administer改名后,win10系统用户文件夹改名的详细步骤
  9. NBA勇士败给残阵湖人后....
  10. NOIP2018赛后总结