自定义ClassLoader
自定义ClassLoader()
public class MyClassLoader extends ClassLoader {
//定义默认的class存放路径
private final static Path DEFAULT_CLASS_PATH = Paths.get("/Users/xxx/Desktop/classloader1");
private final Path classDir;
//使用默认的class路径
public MyClassLoader() {
super();
this.classDir = DEFAULT_CLASS_PATH;
}
//允许传递指定的class路径
public MyClassLoader(Path classDir){
super();
this.classDir = Paths.get(String.valueOf(classDir));;
}
//允许传递指定的class路径和父类加载器
public MyClassLoader(Path classDir, ClassLoader parent){
super();
this.classDir = Paths.get(String.valueOf(classDir));;
}
/**
* 根据类的全名称转换成文件的全路径重写findClass方法
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = this.readClassBytes(name);
//为null则抛出异常
if(null == classBytes || classBytes.length == 0){
throw new ClassNotFoundException("Cant not load the calss" + name);
}
return this.defineClass(name,classBytes,0,classBytes.length);
}
//将class文件读入内存
private byte[] readClassBytes(String name) throws ClassNotFoundException {
String calassPath = name.replace(".","/");
Path classFullPath = classDir.resolve(Paths.get(calassPath + ".class"));
if (!classFullPath.toFile().exists()){
throw new ClassNotFoundException("The class " + name + "not found");
}
try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
Files.copy(classFullPath,baos);
return baos.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException("load the class " + name + "occur error.",e);
}
}
@Override
public String toString() {
return "MyClassLoader{" +
"classDir=" + classDir +
'}';
}
}
defineClass方法
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
参数解释
String name : 自定义类的名字,就是findClass方法中的 String name
byte[] b : class文件的二进制字节数组。
int off : 偏移量
int len : 表示从偏移量off开始往后读取多少个byte
loadClass方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查类是否已经加载;见源码2
Class<?> c = findLoadedClass(name);
//如果类 == null
if (c == null) {
long t0 = System.nanoTime();
try {
//并且当前类存在父类加载器,则调用父类加载器的loadClass(name,fase)方法进行加载
if (parent != null) {
//在这里赋值resolve为false,所以最后的if不会执行
c = parent.loadClass(name, false);
} else {
//并且当前类不存在父类加载器,则调用根加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass方法对其进行加载
if (c == null) {
long t1 = System.nanoTime();
//当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
c = findClass(name);
// 当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
//这也就解释了为什么通过类加载器加载类并不会导致类的初始化。
if (resolve) {
resolveClass(c);
}
return c;
}
}
问题:相同类加载器加载同一个class的实例是相同的吗?
测试源码
private static void demo2() throws ClassNotFoundException {
//同一个类加载器的不同实例去加载同一个class文件,则会在堆内存和方法区产生多个class对象
MyClassLoader classLoader = new MyClassLoader(Paths.get("/Users/yingtaowang/Desktop/classloader1"),null);
MyClassLoader classLoader1 = new MyClassLoader(Paths.get("/Users/yingtaowang/Desktop/classloader1"),null);
Class<?> aClass = classLoader.loadClass("com.example.logbackdemo.com.wangwenjun.concurrent.chapter10.Hello");
Class<?> bClass = classLoader1.loadClass("com.example.logbackdemo.com.wangwenjun.concurrent.chapter10.Hello");
System.out.println(aClass.getClassLoader());
System.out.println(bClass.getClassLoader());
System.out.println(aClass.hashCode());
System.out.println(bClass.hashCode());
System.out.println(aClass == bClass);
}
上述代码的运行结果:
MyClassLoader{classDir=/Users/yingtaowang/Desktop/classloader1}
MyClassLoader{classDir=/Users/yingtaowang/Desktop/classloader1}
752848266
815033865
false
对于相同类加载器的不同实例加载同一个class文件,会得出不同的两个class实例
我们根据这个问题来再看一次loadClass的源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查类是否已经加载;关键在findLoadedClass(name),见源码2
Class<?> c = findLoadedClass(name);
//如果类 == null
if (c == null) {
long t0 = System.nanoTime();
try {
//并且当前类存在父类加载器,则调用父类加载器的loadClass(name,fase)方法进行加载
if (parent != null) {
//在这里赋值resolve为false,所以最后的if不会执行
c = parent.loadClass(name, false);
} else {
//并且当前类不存在父类加载器,则调用根加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass方法对其进行加载
if (c == null) {
long t1 = System.nanoTime();
//当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
c = findClass(name);
// 当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
//这也就解释了为什么通过类加载器加载类并不会导致类的初始化。
if (resolve) {
resolveClass(c);
}
return c;
}
}
源码2
findLoadedClass(String name) And findLoadedClass0(String name)
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
在类加载器进行类加载的时候,首先会到加载记录表也就是缓存中,查看该类是否已经被加载过了,如果已经被加载过了,就不会重复加载,否则将会认为其是首次加载。