实现原理
每个类加载对应1个加载目录,当目录中jar文件被加载后就不能在重新加载,如果要重新加载有2种方式:
1)使用agent热更
2)关闭旧的类加载器,用新创建的类加载器重新加载相同目录中的jar文件,去替换旧的类加载器
本文采用第二种方式实现
自定义类加载器
作用加载指定目录中的jar文件
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
public class DynamicJarClassLoader extends URLClassLoader {
private static boolean canCloseJar = false;
private List cachedJarFiles;
static {
// JDK1.7以上版本支持直接调用close方法关闭打开的jar
// 如果不支持close方法,需要手工释放缓存,避免卸载模块后无法删除jar
try {
URLClassLoader.class.getMethod("close");
canCloseJar = true;
} catch (NoSuchMethodException e) {
System.out.println(e);
} catch (SecurityException e) {
System.out.println(e);
}
}
public DynamicJarClassLoader(String libDir, ClassLoader parent) {
super(new URL[]{}, null == parent ? Thread.currentThread().getContextClassLoader() : parent);
File base = new File(libDir);
URL[] urls = null;
if (null != base && base.canRead() && base.isDirectory()) {
File[] files = base.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.getName().contains(".jar")) {
return true;
} else return false;
}
});
urls = new URL[files.length];
for (int j = 0; j < files.length; j++) {
try {
URL element = files[j].toURI().normalize().toURL();
System.out.println("Adding '" + element.toString() + "' to classloader");
urls[j] = element;
} catch (MalformedURLException e) {
System.out.println(e);
}
}
}
init(urls);
}
private void init(URL[] urls) {
cachedJarFiles = canCloseJar ? null : new ArrayList();
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 e) {
System.out.println(e);
}
} else {
for (JarURLConnection conn : cachedJarFiles) {
conn.getJarFile().close();
}
cachedJarFiles.clear();
}
}
}
测试程序
public class DynamicJarApp {
final static String libDir = "f://lib";
final static String testClass = "cn.test.TestClass";
static URLClassLoader currentClassload;
static long changeLastModified=0;
/**
* 根据修改时间判断文件是否修改
* @param libDir
* @return
*/
public static long changeJarVersion(String libDir) {
long lastModified = 0;
File base = new File(libDir);
if (null != base && base.canRead() && base.isDirectory()) {
File[] files = base.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.getName().contains(".jar")) {
return true;
} else return false;
}
});
for (int j = 0; j < files.length; j++) {
lastModified += files[j].lastModified();
}
}
return lastModified;
}
public static void main(String[] args) {
Thread thead=null;
try {
changeLastModified=changeJarVersion(libDir);
currentClassload= new DynamicJarClassLoader(libDir, null);
Class> clazz = currentClassload.loadClass(testClass);
System.out.println(clazz.getName());
Object object= clazz.newInstance();
Method method= clazz.getDeclaredMethod("info");
method.setAccessible(true);
method.invoke(object);
thead=new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
System.out.println("wait......");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long lastModified = changeJarVersion(libDir);
//jar包版本是否修改
if (changeLastModified != lastModified) {
try {
//关闭旧的类加载器
currentClassload.close();
System.out.println("close......");
} catch (IOException e) {
e.printStackTrace();
}
finally {
changeLastModified = lastModified;
}
//创建新的类加载器
DynamicJarClassLoader newDynamicJarClassLoader = new DynamicJarClassLoader(libDir, currentClassload.getParent());
currentClassload = newDynamicJarClassLoader;
}
try {
//执行jar包类方法
Class> clazz = currentClassload.loadClass(testClass);
System.out.println(clazz.getName());
Object object = clazz.newInstance();
Method method = clazz.getDeclaredMethod("info");
method.setAccessible(true);
method.invoke(object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
thead.start();
} catch (Exception e) {
e.printStackTrace();
}
try {
thead.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类加载器重新加载Jar
将TestClass的2个版本,分别打成2个jar包,然后先放1个到f:/lib目录中,等 DynamicJarApp 运行1段时间在交替更新jar包
public class TestClass {
public void info(){
System.out.println("test class");
}
}
public class TestClass {
public void info(){
System.out.println("new test class");
}
}
程序执行结果
Adding 'file:/f:/lib/lib.jar' to classloader
cn.test.TestClass
test class
wait......
cn.test.TestClass
test class
wait......
cn.test.TestClass
test class
wait......
close......
Adding 'file:/f:/lib/lib.jar' to classloader
cn.test.TestClass
new test class
wait......
cn.test.TestClass
new test class
wait......