需求
有个新需求,希望能热更新的class到虚拟机中
于是需要写一个新Class并且热更到虚拟机中
JAVA类加载器
简单介绍一下系统提供的类加载器
1)Bootstrap ClassLoader
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
2)Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoader
负责记载classpath中指定的jar包及目录中class
4)Custom ClassLoader
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
双亲委派
JAVA的类加载遵循双亲委派模型,简单说,加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
当然有几种不遵循双亲委派模型的,在此不赘述
编码过程
1.单纯的实现加载一个类
URLClassLoader ucl = new URLClassLoader(new URL[]{fileNew.toURI().toURL()});
Class<?> clz = ucl.loadClass("com.Test");
Object o = clz.newInstance();
在这段代码下面,可以继续写你的代码,调用这个类,没有问题
但是我们想实现的不是这样,是想让这个类在虚拟机中任何一段代码都可见
可以调用AppClassLoader
2. 调用AppClassLoader 加载Class
我们只需要将Test.class拷贝到ClassPath中的一个目录就行,包括包,比如lib目录
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clz = ucl.loadClass("com.Test");
这样,这个类就加入到了虚拟机中,可以任意调用
3. 调用AppClassLoader 加载JAR,动态添加新JAR包
首先,我们需要调用URLClassLoader的addURL方法添加新的JAR包路径,但是这个方法是私有的,没关系,反射
URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
add.setAccessible(true);
add.invoke(appClassLoader, new Object[] { url });
这样,我们就可以直接热更新的JAR包里新的类到代码里
还有个问题,就是如果我第一次加载了A.jar(包括Test.class), 我第二次又想加载A.jar(包括Test1.class)
这时候就有问题了,报错找不到Test1.class,这是因为ClassLoader已经缓存了A.jar
怎么办,继续反射清理
URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Field ucpField = URLClassLoader.class.getDeclaredField("ucp");
ucpField.setAccessible(true);
URLClassPath ucp = (URLClassPath) ucpField.get(appClassLoader);
Field pathField = URLClassPath.class.getDeclaredField("path");
pathField.setAccessible(true);
Field urlsField = URLClassPath.class.getDeclaredField("urls");
urlsField.setAccessible(true);
ArrayList<URL> path = (ArrayList<URL>) pathField.get(ucp);
Stack<URL> urls = (Stack<URL>) urlsField.get(ucp);
path.remove(url);
urls.remove(url);
Field loadersField = URLClassPath.class.getDeclaredField("loaders");
loadersField.setAccessible(true);
Field lmapField = URLClassPath.class.getDeclaredField("lmap");
lmapField.setAccessible(true);
ArrayList loaders = (ArrayList) loadersField.get(ucp);
Map lmap = (Map) lmapField.get(ucp);
String urlNoFragString = URLUtil.urlNoFragString(url);
Object o = lmap.remove(urlNoFragString);
if (o != null) {
loaders.remove(o);
}
JAVA是否能反射内部类
我们是否能在代码里反射内部类呢,答案是可以的,代码里连类都不能调用,那怎么反射啊 这样反射
Field field =ClassLoader.getSystemClassLoader().loadClass("com.Test$Inner").getDeclaredField("ucp");
或者
Field field = Class.forName("com.Test$Inner").getDeclaredField("ucp");
有个新需求,希望能热更新的class到虚拟机中
于是需要写一个新Class并且热更到虚拟机中
JAVA类加载器
简单介绍一下系统提供的类加载器
1)Bootstrap ClassLoader
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
2)Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoader
负责记载classpath中指定的jar包及目录中class
4)Custom ClassLoader
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
双亲委派
JAVA的类加载遵循双亲委派模型,简单说,加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
当然有几种不遵循双亲委派模型的,在此不赘述
编码过程
1.单纯的实现加载一个类
URLClassLoader ucl = new URLClassLoader(new URL[]{fileNew.toURI().toURL()});
Class<?> clz = ucl.loadClass("com.Test");
Object o = clz.newInstance();
在这段代码下面,可以继续写你的代码,调用这个类,没有问题
但是我们想实现的不是这样,是想让这个类在虚拟机中任何一段代码都可见
可以调用AppClassLoader
2. 调用AppClassLoader 加载Class
我们只需要将Test.class拷贝到ClassPath中的一个目录就行,包括包,比如lib目录
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clz = ucl.loadClass("com.Test");
这样,这个类就加入到了虚拟机中,可以任意调用
3. 调用AppClassLoader 加载JAR,动态添加新JAR包
首先,我们需要调用URLClassLoader的addURL方法添加新的JAR包路径,但是这个方法是私有的,没关系,反射
URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
add.setAccessible(true);
add.invoke(appClassLoader, new Object[] { url });
这样,我们就可以直接热更新的JAR包里新的类到代码里
还有个问题,就是如果我第一次加载了A.jar(包括Test.class), 我第二次又想加载A.jar(包括Test1.class)
这时候就有问题了,报错找不到Test1.class,这是因为ClassLoader已经缓存了A.jar
怎么办,继续反射清理
URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Field ucpField = URLClassLoader.class.getDeclaredField("ucp");
ucpField.setAccessible(true);
URLClassPath ucp = (URLClassPath) ucpField.get(appClassLoader);
Field pathField = URLClassPath.class.getDeclaredField("path");
pathField.setAccessible(true);
Field urlsField = URLClassPath.class.getDeclaredField("urls");
urlsField.setAccessible(true);
ArrayList<URL> path = (ArrayList<URL>) pathField.get(ucp);
Stack<URL> urls = (Stack<URL>) urlsField.get(ucp);
path.remove(url);
urls.remove(url);
Field loadersField = URLClassPath.class.getDeclaredField("loaders");
loadersField.setAccessible(true);
Field lmapField = URLClassPath.class.getDeclaredField("lmap");
lmapField.setAccessible(true);
ArrayList loaders = (ArrayList) loadersField.get(ucp);
Map lmap = (Map) lmapField.get(ucp);
String urlNoFragString = URLUtil.urlNoFragString(url);
Object o = lmap.remove(urlNoFragString);
if (o != null) {
loaders.remove(o);
}
JAVA是否能反射内部类
我们是否能在代码里反射内部类呢,答案是可以的,代码里连类都不能调用,那怎么反射啊 这样反射
Field field =ClassLoader.getSystemClassLoader().loadClass("com.Test$Inner").getDeclaredField("ucp");
或者
Field field = Class.forName("com.Test$Inner").getDeclaredField("ucp");