1. ClassLoader与classpath
- ClassLoader:类加载器,在Java程序中用于实现类的加载动作,但是其作用远远不限于类加载。在Java中提供了3种ClassLoader分别用于加载不同的Jar包。
- classpath:简单来说,classpath就是每个ClassLoader加载class文件时,寻找class的地方。比如Java中提供的3种ClassLoader其都有自己的classpath。
前面提到,ClassLoader时用于实现类加载,但其作用不限于类加载。是因为在Java中,对于任意一个类,都需要由加载它的类加载器和这个类本身一起确定其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。也就是说,在判断两个类是否“相等”时,前提是这两个类要是在同一个类加载器加载下才会有意义。也就是说,对于两个来自于同一个class文件的类来说,只要其加载它的类加载器不一致,那么这两个类肯定不相等。
如下例:
public static void main(String[] args) throws Exception {
URL url = new URL("file:/.m2/repository/com/alibaba/fastjson/1.2.58/fastjson-1.2.58.jar");
String className = "com.alibaba.fastjson.JSONArray";
URLClassLoader classLoaderLeft = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
Class<?> leftClass = classLoaderLeft.loadClass(className);
Class<?> leftClassOth = classLoaderLeft.loadClass(className);
URLClassLoader classLoaderRight = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
Class<?> rightClass = classLoaderRight.loadClass(className);
System.out.println(leftClass == rightClass);
System.out.println(leftClass == leftClassOth);
}
结果如下:
false
true
在该例中,通过URLClassLoader创建了两个不同的类加载器,去加载同一个包下的同一个class文件。其中leftClass和leftClassOth由于是使用同一个类加载器加载,所以结果为true,leftClass和rightClass是不同类加载器加载,所以结果为false。
注:上例存在的前提条件是,在当前的classpath下,不存在fastjson-1.2.58.jar的包,否则全部返回true,这和双亲委派模型有关系。
2. Java中的ClassLoader
Java中提供了3种类ClassLoader,分别是BootstrapClassLoader,ExtClassLoader,AppClassLoader。其中BootstrapClassLoader在HotSpot虚拟机中使用C++语言实现,ExtClassLoader,AppClassLoader则使用Java语言实现。这3种ClassLoader其classpath也不一样,如下:
- BootstrapClassLoader:启动类加载器,这个类加载器负责加载$JAVA_HOME/lib下,或者被-Xbootclasspath参数所指定的路径中的,且是虚拟机识别的类库加载到虚拟机内存中。
- ExtClassLoader:扩展类加载器,这个类加载器负责加载$JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
- AppClassLoader:应用程序类加载器,这个类加载器负责加载用户类路径上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,那么默认就是该类加载器。比如java -jar,java -cp。
3. 双亲委派模型
3.1 双亲委派是啥
通俗的来说就是,在类加载的过程中,优先是用父类加载器加载,父加载器无法加载在加载。在Java中3种类加载器以及自定义的类加载器遵循一定的层级关系,这种层级关系被称为类加载器的双亲委派模型(parents delegation model)。如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQcij85d-1584368321009)(/Users/noone/markdown-doc/Java ClassLoader/parents-delegation-modle.png)]
3.2 为什么存在双亲委派
首先,双亲委派并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。双亲委派使得Java类随着它的类加载器一起举杯了一种带有优先级的层级关系。例如类java.lang.Object,在rt.jar中。在双亲委派模型下加载,无论是使用任何类加载器加载,最终都会委托给顶层的BootstrapClassLoader加载。如果不使用双亲委派模型,那么在各个类加载器下都有自己的java.lang.Object的class,如同第一节的例子中一样存在java.lang.Object“不等于”java.lang.Object的情况。
4. 使用ClassLoader动态加载类
4.1 使用反射动态加载
在Java提供的三种ClassLoader中,AppClassLoader是负责加载用户类路径上的类库,也就是说初始线程的 getContextClassLoader就是AppClassLoader。AppClassLoader继承自URLClassLoader,URLClassLoader是可以从多个URL中查找class并通过loadClass加载。在URLClassLoader中,存在一个addURL的protected方法,该方法用于向该ClassLoader中添加包含class文件的URL。反射代码如下:
URL url = new URL("file:/.m2/repository/com/alibaba/fastjson/1.2.58/fastjson-1.2.58.jar");
Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
addUrlMethod.invoke(contextClassLoader, url);
Thread.currentThread().getContextClassLoader()在默认情况下,获得的是AppClassLoader。通过反射将fastjson-1.2.58.jar动态追加到AppClassLoader。
4.2 设置上下文类加载器
在上例中,Thread.currentThread().getContextClassLoader()获得到的Thread Context ClassLoader同样提供了能够修改Thread Context ClassLoader的方法,可新建一个URLClassLoader然后将该ClassLoader设置为指定的线程的Thread Context ClassLoader,便可在该线程中使用。代码如下:
URL url = new URL("file:/Users/noone/.m2/repository/com/alibaba/fastjson/1.2.58/fastjson-1.2.58.jar");
String className = "com.alibaba.fastjson.JSONArray";
URLClassLoader classLoaderLeft = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
Class<?> leftClass = classLoaderLeft.loadClass(className);
Class<?> leftClassOth = classLoaderLeft.loadClass(className);
Thread.currentThread().setContextClassLoader(classLoaderLeft);
~~ 以上为个人理解,由于水平有限,如有疏漏,望多多指教,更欢迎多多交流~~