背景
有一个网友看了我的《Flink的classLoader加载机制(推测)-- 记一次程序问题中的探索》这篇文章,向我提问了一个问题,虽然这个问题我没有解决,但是我打算做一个小实验来验证一下解决思路的可行性。问题如下:
得知该朋友已经解决此问题,我只想到了前期思路,后续的class not found问题其实是jobmanager和taskmanager不在一个jvm中导致的,很厉害,学习了。他的文章:https://www.toutiao.com/i6883793897495986691/
实验
因为我现在没有一套随便实验的flink集群,也没有阿里云的oss,所以就只好做个最简单的实验,证明我是可以从远程加载jar包然后使用jar包中的类的。
首先,需要先创建一个maven工程,创建一个类叫做Remote,随便写一个可以打印东西的方法,然后把这个工程打成一个jar包。(当然,如果已经有一个jar包了,那就直接用也行)
然后,在jar包所在的路径下,用python启动一个http服务,命令: python -m SimpleHTTPServer 8888
最后用下面的代码就可以加载这个jar包并加载里面的类,然后执行类里的方法了。
public class Test {
public static void main(String[] args) throws MalformedURLException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
URLClassLoader loader = (URLClassLoader) Test.class.getClassLoader();
// 获取本地jar文件的URL
// File jarFile = new File("/Users/zgy/IdeaProjects/test/target/maven-thrift-client-0.0.1-SNAPSHOT.jar");
// URL targetUrl = jarFile.toURI().toURL();
// 获取远程jar文件的URL
URL targetUrl = new URL("http://localhost:8888/target/maven-thrift-client-0.0.1-SNAPSHOT.jar");
// 这个校验是为了避免重复加载的
boolean isLoader = false;
for (URL url : loader.getURLs()) {
if (url.equals(targetUrl)) {
isLoader = true;
break;
}
}
// 如果没有加载,通过反射获取URLClassLoader.allURL方法来加载jar包
if (!isLoader) {
Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
add.setAccessible(true);
add.invoke(loader, targetUrl);
}
// 加载指定的class,然后为其创建对象后执行其方法,这些操作都是用反射去做的
Class<?> remoteClass = loader.loadClass("Remote");
Object remoteInstance = remoteClass.newInstance();
Method method = remoteClass.getDeclaredMethod("func");
method.setAccessible(true);
System.out.println(method.invoke(remoteInstance));
}
}
URLClassLoader既可以加载本地文件系统的jar包,也可以加载远程jar包。我猜测这些应该使用统一的io来实现的。
后记
其实这个实验只是说明了URLClassLoader的能力和使用方法,网上有很多参考资料可以做到这个功能。但是这个方法还有一些缺陷,比如在我使用http协议加载远程jar包的时候,加载速度明显比本地要慢很多。这样的话,其实可以考虑加载之前先下载jar包,然后再加载。(如果下载jar包,是否可以直接下载到ext目录下,让jvm的extensionClassLoader去加载这个jar包呢?)
另外,这个思路需要用反射去调用类和方法,使用起来很麻烦。我一度怀疑自己做的有问题,为啥jvm不需要这么复杂呢?但是仔细一想,jvm在编译期是需要对应的jar包在的,起码对应的接口要有;运行期也是需要有jar包在,加载后直接使用类去运行了。我们这里做的事情,有点像jvm在运行期加载jar包和找到jar包里的类的过程,而不仅仅是在外部调用类和执行类方法。
参考文章
(1)这篇文章代码很丰富,主题是如何加载jar包,后边的一些功能对于本文章来说不太需要看https://www.cnblogs.com/itboys/p/11011585.html
(2)最开始看的这篇文章,比较简单,但也胜在简单 https://blog.csdn.net/fd2025/article/details/80538468