自定义类加载器的流程:
- 继承java.lang.ClassLoader
- 首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回
- 委派类加载器请求给父类加载器,如果父类加载器能够完成 则返回父类加载器加载的Class实例
- 调用本类加载器的findClass(…)方法,试图获取相应的自己码,如果获取得到,则调用defineClass(…)方法导入类型到方法区;如果获取不到字节码或者其他原因失败,返回异常给loadClass(…),loadClass(…)转抛异常,终止加载过程
- 注意,被两个类加载器加载的同一个类,JVM不认为是相同的类
注2:
自定义一个加载字节码文件(.class)的类加载器
package com.feng.ClassTest;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义文件系统类加载器
* @author feng
*
*/
// 传入指定的目录,目录下class文件可以正常加载
// com.feng.ClassTest.User --> D:/myjava/ com/feng/ClassTest/User.class
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = null;
// -----------------------
c = findLoadedClass(name); //是否能在已加载类中找到
if(c != null) {
return c; //如果已加载,直接返回
}
ClassLoader parent = this.getParent(); //获得父类加载器,就是应用程序类加载器AppClassLoader
try {
c = parent.loadClass(name); //父类加载器加载该类,父类加载该类过程就使用了双亲委托机制
} catch (Exception e) {
// e.printStackTrace();
}
if(c != null) { // 如果父类加载器成功加载该类
return c; // 返回父类加载器加载的类
}
// -----------------------
byte[] classData = getClassData(name); // 自定义方法获取类字节码
if(classData == null) {
throw new ClassNotFoundException(); // 如果字节数组为空,抛出异常
} else {
c = defineClass(name, classData, 0, classData.length);
}
return c;
}
private byte[] getClassData(String classname) {
// 拼出类class文件地址
String path = rootDir + "/" + classname.replace('.', '/') + ".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp = 0;
while((temp = is.read(buffer)) != -1) {
baos.write(buffer, 0, temp);
}
return baos.toByteArray();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} finally {
try {
if(is != null) {
is.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
测试该类
package com.feng.ClassTest;
/**
* 测试自定义FileSystemClassLoader
* @author Administrator
*
*/
public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException {
// TODO Auto-generated method stub
FileSystemClassLoader loader = new FileSystemClassLoader("E:\\myjava");
FileSystemClassLoader loader2 = new FileSystemClassLoader("E:\\myjava");
Class<?> c1 = loader.findClass("com.feng.Hello.Hello");
Class<?> c2 = loader.loadClass("com.feng.Hello.Hello");
Class<?> c3 = loader2.loadClass("com.feng.Hello.Hello");
Class<?> c4 = loader2.findClass("com.feng.ClassTest.Demo01");
Class<?> c5 = loader2.findClass("java.lang.String");
System.out.println(c1); // class com.feng.Hello.Hello
// 同一个类被不同加载器加载,JVM也认为是不同的类。只有同一个类加载器加载的同一个类JVM才认为是一类
System.out.println(c1.hashCode()); //1550089733
System.out.println(c2.hashCode()); // 1550089733
System.out.println(c3.hashCode()); // 865113938
System.out.println(c3.getClassLoader()); //com.feng.ClassTest.FileSystemClassLoader@4e25154f
System.out.println(c4.getClassLoader()); //sun.misc.Launcher$AppClassLoader@73d16e93
System.out.println(c5.getClassLoader()); //null
}
}
注 c2,c3 调用的是loadClass()方法,该方法会调用我们自定义类加载器重写的findClass()方法。loadClass()方法执行过程见上图。也就是说,如果我们写的自定义类都调用loadClass()方法来加载类的话。可以将自定义类加载器//--------包围的那一部分省略,因为loadClass()方法会执行这些步骤,如果加载不到该类才会调用findClass()方法。
// -----------------------
c = findLoadedClass(name); //是否能在已加载类中找到
if(c != null) {
return c; //如果已加载,直接返回
}
ClassLoader parent = this.getParent(); //获得父类加载器,就是应用程序类加载器AppClassLoader
try {
c = parent.loadClass(name); //父类加载器加载该类,父类加载该类过程就使用了双亲委托机制
} catch (Exception e) {
// e.printStackTrace();
}
if(c != null) { // 如果父类加载器成功加载该类
return c; // 返回父类加载器加载的类
}
// -----------------------
当然,如果我们想调用自定义类的findClass()方法去加载如java.lang.String这样的类,那这些就必须写上。
附:自定义网络类加载器。改自文件类加载器。
package com.feng.ClassTest;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
* 网络类加载器
* @author feng
*
*/
// 传入网络url
// com.feng.ClassTest.User --> www.feng.cn/myjava/ com/feng/ClassTest/User.class
public class NetClassLoader extends ClassLoader {
private String rootUrl;
public NetClassLoader(String rootUrl) {
this.rootUrl = rootUrl;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = null;
c = findLoadedClass(name); //是否能在已加载类中找到
if(c != null) {
return c; //如果已加载,直接返回
}
ClassLoader parent = this.getParent(); //获得父类加载器,就是应用程序类加载器AppClassLoader
try {
c = parent.loadClass(name); //父类加载器加载该类,父类加载该类过程就使用了双亲委托机制
} catch (Exception e) {
// e.printStackTrace();
}
if(c != null) { // 如果父类加载器成功加载该类
return c; // 返回父类加载器加载的类
}
byte[] classData = getClassData(name); // 自定义方法获取类字节码
if(classData == null) {
throw new ClassNotFoundException(); // 如果字节数组为空,抛出异常
} else {
c = defineClass(name, classData, 0, classData.length);
}
return c;
}
private byte[] getClassData(String classname) {
// 拼出类class文件地址
String path = rootUrl + "/" + classname.replace('.', '/') + ".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
URL url = new URL(path);
is = url.openStream();
byte[] buffer = new byte[1024];
int temp = 0;
while((temp = is.read(buffer)) != -1) {
baos.write(buffer, 0, temp);
}
return baos.toByteArray();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} finally {
try {
if(is != null) {
is.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
基本一样。只是将地址换成了url,将文件读取换成了网络读取。