在模块化开发的时候,我们经常会把一个模块的内容打包成一个FAT JAR
,然后加载,实现模块的热加载,对于热加载的实现并不复杂,我们只需要定义自己的加载器,然后继承URLClassLoader
就可以了,但是我们会发现一个问题,加载后的JAR 会被JAVA占用无法删除,所以如果我们的程序里面希望在不停机的情况下完全卸载模块就需要稍微处理一下,幸好,在JDK1.7中的URLClassLoader
提供了close
方法,可以关闭已经打开的JAR文件,释放相关资源,但是如果我们使用的是低于1.7版本的就没办法使用了,不应着急,我们可以曲线救国,来实现已经加载的JAR文件的热卸载。
package com.jinggujin.classloader;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* 动态JAR加载器
*
* @author jianggujin
*
*/
public class DynamicJarClassLoader extends URLClassLoader {
private static boolean canCloseJar = false;
private List<JarURLConnection> cachedJarFiles;
static {
// 1.7之后可以直接调用close方法关闭打开的jar,需要判断当前运行的环境是否支持close方法,如果不支持,需要缓存,避免卸载模块后无法删除jar
try {
URLClassLoader.class.getMethod("close");
canCloseJar = true;
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
}
}
public DynamicJarClassLoader(URL[] urls, ClassLoader parent) {
super(new URL[] {}, parent);
init(urls);
}
public DynamicJarClassLoader(URL[] urls) {
super(new URL[] {});
init(urls);
}
public DynamicJarClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
super(new URL[] {}, parent, factory);
init(urls);
}
private void init(URL[] urls) {
cachedJarFiles = canCloseJar ? null : new ArrayList<JarURLConnection>();
if (urls != null) {
for (URL url : urls) {
this.addURL(url);
}
}
}
@Override
protected void addURL(URL url) {
if (!canCloseJar) {
try {
// 打开并缓存文件url连接
URLConnection uc = url.openConnection();
if (uc instanceof JarURLConnection) {
uc.setUseCaches(true);
((JarURLConnection) uc).getManifest();
cachedJarFiles.add((JarURLConnection) uc);
}
} catch (Exception e) {
}
}
super.addURL(url);
}
public void close() throws IOException {
if (canCloseJar) {
try {
super.close();
} catch (IOException ioe) {
}
} else {
for (JarURLConnection conn : cachedJarFiles) {
conn.getJarFile().close();
}
cachedJarFiles.clear();
}
}
}
这个类就是一种兼容高低版本的一种实现,网上还有使用反射获得ucp
对象,然后释放的,大家可以自行研究,这里不做讨论。简单的看一下这个类的实现原理,首先加载这个类后会执行静态代码块,在静态代码块中判断当前运行的环境是否支持直接调用close
方法,然后修改相应标志位。如果不支持该方法则初始化已加载的JAR文件的缓存,在新增URL的同时添加相关的缓存对象。最后统一各版本的close
方法,当不支持关闭的时候就遍历已加载的缓存对象并对其进行释放操作。这样就可以实现在不停机的情况下,完成已加载JAR的热卸载。