一、简介
自定义类加载器实现方式为,继承ClassLoader类,重写其内部方法。简单介绍一下ClassLoader的与加载类相关的方法:
1.getParent():返回该类加载器的父类加载器。
2.loadClass(String name):加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
3.findClass(String name):查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
4.findLoadedClass(String name):查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。
5.defineClass(String name, byte[] b, int off, int len):把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。
6.resolveClass(Class<?> c):解析指定的 Java 类。
还有一个ClassLoader类型的parent成员变量,存储当前加载器的父加载器。
二、loadClass方法源码解析
loadClass方法实现了双亲委派的概念。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//加锁
synchronized (getClassLoadingLock(name)) {
// 首先,当前加载器检查该类是否已经加载 加载就返回
Class<?> c = findLoadedClass(name);
if (c == null) {
//类加载器为了记录统计数据
long t0 = System.nanoTime();
try {
//如果检查自己缓存没有,则找父加载器也执行loadClass方法 这里是递归
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//返回由引导类加载器加载的指定类 如果找不到则返回null。
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果仍然找不到,按由父到子调用findClass
if (c == null) {
long t1 = System.nanoTime();
//查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例 找不到抛出 ClassNotFoundException 整个方法都是在递归
c = findClass(name);
//类加载器为了记录统计数据
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
//是否解析
if (resolve) {
resolveClass(c);
}
return c;
}
}
三、自定义类加载器
1.自定义一个遵循双亲委派的类加载器:继承ClassLoader类,重写其中当前加载器的findClass方法即可,实现代码如下:
public class FileSystemClassLoader extends ClassLoader {
//class文件位置
private static final String ROOTDIR = "D:/ideaworker/out/production/ideaworker/";
//重写findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
//将字节文件转换为Class对象
return defineClass(name, classData, 0, classData.length);
}
}
//读取字节文件
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//路径拼接转换
private String classNameToPath(String className) {
return ROOTDIR + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
//测试两次加载类返回class对象是否为同一对象
public static void main(String[] args) throws Exception {
FileSystemClassLoader fileSystemClassLoader = new FileSystemClassLoader();
Class clazz1 = fileSystemClassLoader.loadClass("com.company.jvm.test");
fileSystemClassLoader = new FileSystemClassLoader();
Class clazz2 = fileSystemClassLoader.loadClass("com.company.jvm.test");
System.out.println(clazz1 == clazz2);
}
}
输出结果为true,因为该类已经被加载过,遵循双亲委派原理,不会重复加载。
2.自定义一个不遵循双亲委派的类加载器:因为双亲委派原理实现在loadClass方法内,所以重写loadClass方法,不去检查父加载器的加载情况。实现代码如下:
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException{
File f = new File("D:/ideaworker/out/production/ideaworker/" + name.replace(".","/").concat(".class"));
if(!f.exists()){
return super.loadClass(name);
}
try {
InputStream is = new FileInputStream(f);
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
}catch (IOException e){
e.printStackTrace();
}
return super.loadClass(name);
}
//测试两次加载类返回class对象是否为同一对象
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();
Class clazz1 = myClassLoader.loadClass("com.company.jvm.test");
myClassLoader = new MyClassLoader();
Class clazz2 = myClassLoader.loadClass("com.company.jvm.test");
System.out.println(clazz1 == clazz2);
}
}
输出结果为false,因为不遵循双亲委派原理,不去递归查找父类是否加载过,只要调用就加载一次,所以两次加载返回对象不一致。
打破双亲委派的情况:热加载(tomcat,osgi相同类库包,版本不同)、线程上下文类加载器(线程运行期间自定义类加载器,默认应用程序类加载器Application ClassLoader, getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 用来获取和设置线程的上下文类加载器)。