Java运行时加载jar包
一、故事背景(发疯言论,可跳过)
有一个调度平台s,你可以上传jar包在平台调度执行
一直以来,均由你一个人负责开发和上传demo.jar,你感觉一切都还不错
但是有一天,突然来了两个同学要和你一起开发维护demo.jar
很多业务并行起来,瞬间拉出三个分支,这让你感到焦虑
于是你想让demo接入一个项目管理平台p,对所有开发分支自动合并打包,而不再是本地打包上传的模式
但是平台p和s无法直接打通,p在编译打包完成后无法将demo.jar自动上传平台s
这就很恼火了,你还得下载demo.jar,再去平台s上传
但还有更可恶的事情,平台s还只允许一个owner上传jar包
换句话说,别人的所有变更都要你来参与这个jar包的下载和上传
于是我决定在平台s上重新上传一个稳定不变的loader.jar
这里面不再直接写业务逻辑,而是去下载平台p打包好的demo.jar然后执行它
于是有了第一次尝试:下载后使用UrlCassLoader实现运行时加载执行jar包(有很多文章已经讲过如何使用它了,这里便不再赘述)
但是作为程序员的你一定料到了,事情不可能这么顺利
当你在本地测试一切顺利后,把loader.jar上传到平台s
exception: file system is readonly
哈哈,不让你下载文件到本地
那为什么一定要写到文件再加载呢,有没有办法直接在内存搞定?
那就让我们来试试吧
二、生成demo.jar
我们创建一个Module:jar-demo
然后简单写一个hello world
package org.example;
public class DemoEntry {
public void entry() {
System.out.println("hello world");
}
}
然后 mvn package 生成jar包
三、把jar包加载到内存,创建自定义classLoader
package org.example.loader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
public class StreamLoadDemo {
public static void main(String[] args) throws Exception {
//这里还是用我们刚刚打包的本地jar包创建输入流,对于网络下载文件可以替换对应的inputStream
String jarPath = System.getProperty("user.dir") + "/jar-demo/target/jar-demo-1.0-SNAPSHOT.jar";
JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(Paths.get(jarPath)));
//类名->字节码索引
Map<String, int[]> classByteMap = new HashMap<>();
byte[] buffer = new byte[1024 * 1024];
int head = 0;
int chunk = 256;
JarEntry jarEntry = jarInputStream.getNextJarEntry();
while (jarEntry != null) {
String name = jarEntry.getName();
if (name.endsWith(".class")) {
String className = name.replaceAll("/", ".").replaceAll(".class", "");
int size = 0, read;
while (true) {
read = jarInputStream.read(buffer, head + size, chunk);
if (read == -1) {
break;
}
size += read;
}
classByteMap.put(className, new int[]{head, size});
head += size;
}
jarEntry = jarInputStream.getNextJarEntry();
}
SecureClassLoader classLoader = new SecureClassLoader() {
@Override
protected Class<?> findClass(String name) {
int[] arr = classByteMap.get(name);
return super.defineClass(name, buffer, arr[0], arr[1]);
}
};
Class<?> clazz = classLoader.loadClass("org.example.DemoEntry");
Constructor<?> constructor = clazz.getConstructor();
Object demoEntry = constructor.newInstance();
Method entry = clazz.getDeclaredMethod("entry");
entry.invoke(demoEntry);
}
}
运行结果:
功能实现了,不过我也是知其然不知其所以然,要是大家对于java类加载有更多的了解,对这个功能有更优雅的实现,欢迎在评论区留言~