一.类加载器:根据指定的全限定名将class文件加载到JVM内存,转为class对象。从JVM角度看,分为两种类加载器:
1.启动类加载器(Bootstrap ClassLoader)
由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
2.其他类加载器:
由Java语言实现,继承自抽象类ClassLoader。如:
2.1 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
2.2 应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
二.双亲委派模型
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。
每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。抽象类java.lang.ClassLoader的loadClass(String, boolean)方法实现双亲委派模型:
1.首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
2.如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);),否则调用Bootstrap类加载器来加载。
3.如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
4.抽象类java.lang.ClassLoader的findClass(String)方法默认抛出ClassNotFoundException异常,所以自定义类加载器必须重写findClass(String)方法。
三.编写自定义类加载器
1.首先写两个简单的类用于测试
在IDEA里新建java工程,创建如下包
Demo1.java
package com.classloader;
public class Demo1 {
public Demo1(){
System.out.println("Demo1由" + getClass().getClassLoader().getClass() + "加载");
}
public void hello(){
System.out.println("hello Demo1");
}
}
Demo2.java
package com.classloader;
public class Demo2 {
public Demo2(){
System.out.println("Demo2由" + getClass().getClassLoader().getClass() + "加载");
}
public void hello(){
System.out.println("hello Demo2");
}
}
两个demo编译后,将Demo2.class从项目输出目录下拷贝到本地磁盘D:/test/com/classloader/下,并从项目输出目录中删除
创建自定义类加载器
package com.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @desc: 自定义类加载器
* @date: 2018/7/23 19:29
*/
public class MyClassLoader extends ClassLoader {
/**
* 类加载路径
*/
private String path;
public MyClassLoader(String path) {
this.path = path;
}
/**
* @desc: 重写findClass方法用于加载指定类
* @date: 2018/7/23 17:47
* @param:
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = readFileToByte(name);
return defineClass(name, data, 0, data.length);
}
/**
* @desc: 读取class文件转换为字节数组
* @date: 2018/7/23 17:46
* @param:
*/
private byte[] readFileToByte(String name) {
InputStream is = null;
byte[] result = null;
//将全限定类名
name = name.replaceAll("\\.", "/");
String classPath = path + name + ".class";
File file = new File(classPath);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
is = new FileInputStream(file);
int tmp = 0;
while ((tmp = is.read()) != -1) {
os.write(tmp);
}
result = os.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader classload = new MyClassLoader("D:/test/");
// com.classloader.Demo1
Class clazz = classload.loadClass("com.classloader.Demo1");
Object obj = clazz.newInstance();
Method hello = clazz.getDeclaredMethod("hello", null);
hello.invoke(obj, null);
}
}
执行main方法
从控制台输出可以看出,Demo1.class是由AppClassLoader类加载器加载的。
将main方法中指定的全限定类名改为com.classloader.Demo2
再次执行main方法
可以看出Demo2.class是由我们自己编写的自定义类加载器加载的