每个ClassLoader实例之间是隔离的
自定义的ClassLoader可以使用父ClassLoader加载的类
如果由父ClassLoader加载的类,需要访问子ClassLoader加载的类,可以将子ClassLoader传递到父ClassLoader中。
这里的父子类,并不是java中的继承关系。只是有一个变量指向其他对象。就像链表中每个节点可以指向上个节点
这个方法总是返回要加载的类的Class类的实例
import java.io.*; import java.lang.reflect.*; class AClassLoader extends ClassLoader { public String loadPath = "/Users/fang/untitled/"; // 定义类存放的路径 public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class klass = null; try { klass = findLoadedClass(name); //充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader // 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法 if (klass != null) { System.out.println("already load: "+name); return klass; } if (klass == null) { //如果读取字节失败,则试图从JDK的系统API中寻找该类。 klass = findSystemClass(name);// 调用系统默认的ClassLoader(AppClassLoader)装载类, System.out.println("load with SystemClassLoader: " + name); // 如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。 // 当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。 } if (resolve && klass != null) { resolveClass(klass); // 连接class } } catch (ClassNotFoundException e){// 如果系统无法找到该类,则用自己的方式装载 byte[] bs = getClassBytes(name);//从一个特定的信息源寻找并读取该类的字节。 if (bs != null && bs.length > 0) { klass = defineClass(name, bs, 0, bs.length); // 该方法接受由原始字节组成的数组并把它转换成 Class 对象 System.out.println("load with CustomLoader: " + name); } } catch (Exception e) { throw new ClassNotFoundException(e.toString()); } return klass; } /** * 定义获取类的方法,可以从网络,文件系统等地方获取 * * @param className * @return */ private byte[] getClassBytes(String className) { FileInputStream fis = null; try { fis = new FileInputStream(loadPath+className+".class"); } catch (FileNotFoundException e) { // System.out.println(e); System.out.println("name:"+className); return null; //如果查找失败,则放弃查找。捕捉这个异常主要是为了过滤JDK的系统API。 } try { byte[] bs = new byte[fis.available()]; fis.read(bs); return bs; }catch (Exception e){ throw new RuntimeException(""); } } } public class b { public static void main(String[] args) throws Exception{ AClassLoader loader = new AClassLoader(); Class c = loader.loadClass("A", false); // 类加载 Object o = c.newInstance(); Method m = c.getMethod("print", java.lang.String.class); m.invoke(o, "fang"); } }
public class A{ public void print(String str) { System.out.println("嗨," + str + ", 你终于找到我了!"); } public String toString() { return "我是一个A对象!"; } }
程序说明
AClassLoader加载类A后,需要递归加载接口I。要使程序能正常工作必须保证,AClassLoader加载接口I是必须使用系统加载器,如果用AClassLoader加载接口I,那在程序中将无法转换成接口I类型。因为程序中的接口I是由系统加载器加载的,而类A的接口I是由AClassLoader加载的
import java.io.*; class AClassLoader extends ClassLoader { public String loadPath = "/Users/fang/untitled/"; // 定义类存放的路径 public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class klass; klass = findLoadedClass(name); //充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader // 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法 if (klass != null) { System.out.println("already load: "+name); return klass; } // 根据需要确定是先调用系统类装载器,还是自定义装载器 try{ klass = getClassWithSystemClassLoader(name); }catch (Exception e){ klass = getClassSelf(name); } if (resolve && klass != null) resolveClass(klass); // 连接class return klass; } /** * 调用系统装载器 */ private Class getClassWithSystemClassLoader(String name){ try { //如果读取字节失败,则试图从JDK的系统API中寻找该类。 Class c = findSystemClass(name);// 调用系统默认的ClassLoader(AppClassLoader)装载类, System.out.println("load with SystemClassLoader: " + name); // 如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。 // 当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。 return c; } catch (ClassNotFoundException e){ // 如果系统无法找到该类,则用自己的方式装载 throw new RuntimeException(e); } } /** * 使用自己的装载器 */ private Class getClassSelf(String name){ //从一个特定的信息源寻找并读取该类的字节。 try { byte[] bs = getClassBytes(name); System.out.println("load with CustomLoader: " + name); return defineClass(name, bs, 0, bs.length); // 该方法接受由原始字节组成的数组并把它转换成 Class 对象 }catch (ClassFormatError e){ throw new RuntimeException(e); } } /** * 定义获取类的方法,可以从网络,文件系统等地方获取 */ private byte[] getClassBytes(String className) { FileInputStream fis = null; try { fis = new FileInputStream(loadPath+className+".class"); byte[] bs = new byte[fis.available()]; fis.read(bs); return bs; }catch (Exception e){ try { if(fis != null) fis.close(); } catch (IOException e1) { e1.printStackTrace(); } throw new RuntimeException(e); } } } public class B { public static void main(String[] args) throws Exception{ AClassLoader loader = new AClassLoader(); Class c = loader.loadClass("A", false); // 类加载 I o = (I)c.newInstance(); o.print("123"); } }
public interface I { public void print(String s); }
public class A implements I{ public void print(String str) { System.out.println("hello " + str); } }
热替换很难做,因为同一个classloader不能加载2个同名的类。也就是说热替换的前提是旧的class对象被回收,才能加载新的类对象
类回收(unload)条件 (总之卸载类完全无法控制)
当然可以退而求其次,替换新new的对象。旧对象无法改变
所以如果要热替换,最好是替换一些计算逻辑,不要替换数据结构
下面代码实现了热替换静态方法
import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class App { // 定义一个可能被热替换的函数 static Method method = null; public static void main(String[] args) throws Exception{ // 初始化函数 method = App.class.getMethod("print", String.class); new Thread(){ public void run(){ for(;;){ try { Thread.sleep(1000); method.invoke(null, "fang"); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } }.start(); for(;;) { Thread.sleep(1000); // 热加载的jar包位置 File file = new File("/Users/fang/Documents/test1/tmppp/A.jar"); URL url = file.toURL(); URL[] urls = new URL[]{url}; ClassLoader cl = new URLClassLoader(urls); // 加载类aa.bb.A Class c = cl.loadClass("aa.bb.A"); // 更新目标方法 method = c.getMethod("print", String.class); } } public static void print(String str) { System.out.println("A: " + str); } }