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.6
mysql-connector-java 5.1.35
hive 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/demo
user:test
pwd:test
hive3.1.X 版本数据源连接信息
url:jdbc:hive2://127.0.0.1:2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2
user:test
pwd: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;
}
/**