前言
类加载器相关的文章很多,使用自定义什么的一搜一堆这里不说
下面分析已经是把分析时候的岔路都去掉直接写的正确结论但还是有点长,没有耐心的可以直接看最后的结论
正文
问题描述
在成功实现了一个自定义类加载器之后就产生了一个疑问
例如我编译出来了两个clss文件 a 和 b,其中a里面引用了b的和String,b中引用了String实例。
问题来了此时a中的string和b是哪个加载器加载的?b中的string是哪个加载器加载的?
问题分析
先假设
1.站在java的角度想,如果要让这个场景正常工作 只能让a、b两个类由自定义加载器加载,String由启动类加载器加载才可以。因为a、b是我从其他位置引入进来的系统类加载器肯定加载不到,String还只能有启动类加载器加载。
2.如果实现步骤1所描述的,应该是从自定义类加载器直接加载的那个类开始,里面涉及到的所有类(间接引用的,例如b和String)都由自定义类加载器来加载,并且自定义类加载器不能破坏双亲委派模型(不重写ClassLoader#loadClass方法)。
在验证
我是验证之后才来写的文章,类名不太一致这里标记一下 a=Other1;b=Other3;
首先弄两个类Other1(a)和Other3(b)
public class Other1 {
public void test(){
// 引用string并获取classloader
System.out.println("other1#string-loader:"+String.class.getClassLoader());
// 打印自己的classloader
System.out.println("other1#test:"+getClass().getClassLoader());
// 引用other3
new Other3().test();
}
}
public class Other3 {
public void test() {
// 引用string并获取classloader
System.out.println("other3#string-loader:"+String.class.getClassLoader());
// 打印自己的classloader
System.out.println("other3#test:"+getClass().getClassLoader());
}
}
自定义类加载器进行调用
package com;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 辅dasd导教师狂dsadd蜂浪蝶就撒asd了23e21dsa3
* @author wb-ycl473317
*/
public class MyClassLoader extends ClassLoader {
/**
* 重写classloader的findClass方法
* 在loadClass方法中当父类找不到对应类时会调用该方法
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clazz=null;
String prePath="D:\\javacache\\eclipse\\test1\\target\\classes\\";
name=name.replace("test.","test\\")+".class";
try(FileInputStream filterInputStream = new FileInputStream(prePath+name)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 读取硬盘上的clas文件转成二进制数组
filterInputStream.transferTo(bos);
byte[] bytes = bos.toByteArray();
// 通过调用defineClass方法获取class对象
clazz = defineClass(null,bytes,0,bytes.length);
}catch (Exception e){
e.printStackTrace();
}
return clazz;
}
public static void main(String[] args) throws Exception {
//初始化自定义类加载器加载指定class文件 生成class对象
Class<?> aClass = new MyClassLoader().findClass("test.Other1");
// 获取到class对象对应的构造器(这里获取的无参构造器)
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
// 反射获取类实例
Object o = declaredConstructor.newInstance();
// 反射获取方法
Method log = aClass.getMethod("test");
// 执行方法
log.invoke(o);
}
}
运行后结果
other1#string-loader:null
other1#test:com.MyClassLoader@340f438e
other3#string-loader:null
other3#test:com.MyClassLoader@340f438e
结果分析
从结果可以看见 两个类中的string的加载器都为null表示使用的是启动类加载器,other1和other3使用的是MyClassLoader是我们自定义的类加载器。
到这里其实可以说明自定义类加载器间接引用的类也是通过自定义类加载器来加载的,但是还不能证明间接引用的类java到底是怎么个调用顺序来加载的,要下班了、、、突然不想写了 不贴代码了(就是基于上面代码的改动)直接写文字吧
1.通过在MyClassLoader#findClass中打打断点可以发现这个代码被调用了两次,看传入的参数可以发现两次的参数分别是test.Other1和test.Other3。test.Other1我们知道是我们自己写在main方法中的,那test.Other3我们没有写那只有一种情况就是java虚拟机加载other3这个类的时候调用了我们的方法,那为什么只有两次string没有调用我们的方法呢?
2.string肯定是加载了但是没有走我们的findClass方法那肯定就是java虚拟机调用了loadClass方法,被双亲委派模式传递到启动类加载了,要验证这个问题只需要破坏委派模式,如果string加载失败了那就证明这个猜想是对的。
3.把MyClassLoader#findClass名字改成loadClass然后main方法中第一行代码直接调用loadClass方法来获取类(目的是为了破坏委派模式不让他去父级查找),运行代码发现报错了,object、string等无法初始化。
结论
自定义类加载器在加载类时,对于直接加载的类和间接加载的类都是使用我们自定义的类加载器进行加载,且间接加载的类是调用loadClass方法进行加载的(类加载器正确是使用姿势就是重新findClass然后调用loadClass获取的)。
所以自定义类加载器双亲委派模式还不不要破坏的好,不然jdk里的类我们就找不到了、、、