在 https://blog.csdn.net/zero__007/article/details/80428632 中介绍了生成可运行jar包的方式,来想想,能不能把依赖jar包也打包进入jar包,注意不是把第三方jar包的源码打进去。
事实是,是可以的,下面来介绍这个奇技淫巧。主要思路是使用ClassLoader来实现。首先是打jar包:
jar {
manifest {
attributes "Implementation-Title": project.name
attributes "Implementation-Version": '1.0.0'
attributes 'Main-Class': 'org.zero.jar.ResourceLoadFromJarUtil'
}
into('lib'){
from configurations.runtime
}
}
我们知道java中双亲委派模式,我们是不是可以把在jar包里面的第三方jar包解出来,然后用URLClassLoader
加载一下,再把这个URLClassLoader
的parent设置为Thread.currentThread().getContextClassLoader()
,再把这个URLClassLoader
通过Thread.currentThread().setContextClassLoader()
绑到线程上,不就OK了吗?诚然,这种方式我们可以使用loadClass(“xxx.xxx.xxx”)
的方式来加载到某个类,但是我们无法使用XXX xxx = new XXX()
的方式来初始化第三方的类。
我们知道java中最基本的ClassLoader
是AppClassLoader
,它是默认的系统加载器,这个ClassLoader
实际上也是URLClassLoader
的子类。当使用XXX xxx = new XXX()
的方式加载类时使用的就是这个,我们是不是可以通过addURL
方法把我们的第三方jar加到这个AppClassLoader
呢?答案是可以的:
public class ResourceLoadFromJarUtil {
public static void main(String[] args) {
Map<String, String> map = Maps.newHashMap();
map.put("hello", "world");
System.out.println(map);
}
static {
loadJarsInJar();
}
private static void loadJarsInJar() {
// 非java -jar 启动时,java.class.path中包含 rt.jar 等核心jar,因此依据这个来判断是否需要addURL
String[] jars = System.getProperty("java.class.path").split(":");
for (String jar : jars) {
if (jar.endsWith("rt.jar")) {
return;
}
}
List<File> fileList = new ArrayList<>();
for (String jar : jars) {
if (!jar.endsWith(".jar")) {
return;
}
try (JarFile jarFile = new JarFile(jar)) {
for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
JarEntry jarEntry = e.nextElement();
if (jarEntry.getName().endsWith("jar")) {
File f = convert(jarEntry.getName(), jarFile.getInputStream(jarEntry));
fileList.add(f);
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
// Use reflection
try {
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
for (File file : fileList) {
method.invoke(classLoader, file.toURI().toURL());
file.deleteOnExit();
}
Thread.currentThread().setContextClassLoader(classLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
private static File convert(String name, InputStream inputStream) {
try {
File file = new File(name);
OutputStream os = new FileOutputStream(file);
int bytesRead;
byte[] buffer = new byte[8192];
while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
return file;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
参考:https://stackoverflow.com/questions/252893/how-do-you-change-the-classpath-within-java