在学习反射时,实现了自定义ClassLoader类加载器,我们都知道点击运行后.java
代码文件在编译成.class
字节码文件后,jvm自动加载.class
文件,而在自定义类加载器后,不再是jvm自动加载,而是手动选择加载编译后的.class
文件,此文是研究class文件从加载到运行中的具体过程.
本人在学习中参考的是B站up主 青空の霞光 的视频,讲的挺好,反射不明白的可以去看看,吐槽一句,人与人之间是不能比较的,在看到自定义classLoader类加载器时,up主一句"这是我高中研究出的一个玩意"把我惊到了.
测试类
ReflectTest.java是一个可以随意编写的测试类
这里是我自己写的
package test;
public class ReflectTest {
public String name;
public int age;
private String id;
public String password;
public ReflectTest(){}
public ReflectTest(String name){
this.name = name;
}
private ReflectTest(int age){
this.age = age;
}
public ReflectTest(String name,int age){
this.name = name;
this.age = age;
}
public void say() {
System.out.println(name+"12345");
}
public void sayAge(){
System.out.println("age:"+age);
}
public void saySome(String word){
System.out.println("this is myword:"+word);
}
private void sayHello(String name){
System.out.println("Hello "+name);
}
private void sayHellos(String... names){
for (String item:names
) {
System.out.println("Hello"+item);
}
}
public void sayId(){
System.out.println("id:"+id);
}
}
第一步:执行命令javac .\src\Test\Student.java 得到Student.class字节码文件
然后会多出一个.class文件,如下图
我们把ReflectTest的源文件删除
自定义加载类
下面是我们加载ReflectTest的测试类,其中自定义类加载器放在Test12类中
package reflect;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//自定义加载器加载class文件,可运行时加载
public class Test12 {
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();
FileInputStream stream = new FileInputStream("src\\Test\\ReflectTest.class");//填ReflectTest.class的地址
byte[] bytes = new byte[stream.available()];
stream.read(bytes);
Class<?> clazz = myClassLoader.defineClass("test.ReflectTest",bytes); //必须和我们定义的完整类名一致
System.out.println(clazz.getName()); //加载成功
stream.close();
try {
Object object = clazz.newInstance(); //用object接收ReflectTest的实例
Method method = clazz.getMethod("saySome",String.class);
method.invoke(object,"HuGuang");
Field field = clazz.getField("age"); //获取属性
field.set(object,23); //设置age属性
Method method1 = clazz.getMethod("sayAge"); //测试ReflectTest类的sayAge方法
method1.invoke(object);
}catch (Exception e){
e.printStackTrace();
}
}
//自定义类加载器
static class MyClassLoader extends ClassLoader{ //自定义class加载器,因为ClassLoader是抽象类不能被实例化
public Class<?> defineClass(String className,byte[] classByte){
return defineClass(className , classByte , 0 , classByte.length);
}
}
}
可以看到在ReflcetTest.java文件被删的情况下,这个类仍然被加载运行,ReflectTest的方法测试成功,也就是说ReflectTest.class文件的确被我们的自定义类MyClassLoader加载.
自定义的加载类MyClassLoader中并没有太多内容,关键点是这句
return defineClass(className , classByte , 0 , classByte.length);
defineClass方法是继承父类ClassLoader的,ReflcetTest.class转化成字节流后最后也是传入defineClass方法中,被defineClass方法加载进JVM,所以我们要弄清楚ReflectTest.class如何被添加到JVM就是要弄清defineClass方法.
解析defineClass
下面是ClassLoader类中defineClass方法的源码
protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length) throws ClassFormatError {
return defineClass(className, classRep, offset, length, null);
}
可以看到参数又被交给另一个重载的defineClass方法处理,跳到这个方法看看
protected final Class<?> defineClass (
final String className,
final byte[] classRep,
final int offset,
final int length,
ProtectionDomain protectionDomain)
throws java.lang.ClassFormatError
{
return defineClassInternal(className, classRep, offset, length, protectionDomain, false /* allowNullProtectionDomain */);
}
这个方法也是没啥用,参数又交给defineClassInternal方法处理,跳过去看看
final Class<?> defineClassInternal(
final String className,
final byte[] classRep,
final int offset,
final int length,
ProtectionDomain protectionDomain,
boolean allowNullProtectionDomain)
throws java.lang.ClassFormatError
{
Certificate[] certs = null;
if (protectionDomain != null) {
final CodeSource cs = protectionDomain.getCodeSource();
if (cs != null) certs = cs.getCertificates();
}
if (className != null) {
String packageName = checkClassName(className);
if ((protectionDomain == null) && allowNullProtectionDomain) {
/*
* Skip checkPackageSigners(), in this condition, the caller of this method is
* java.lang.Access.defineClass() and invoked by trusted system code hence
* there is no need to check its ProtectionDomain and associated code source certificates.
*/
} else {
checkPackageSigners(packageName, className, certs);
}
}
if (offset < 0 || length < 0 || offset > classRep.length || length > classRep.length - offset) {
throw new ArrayIndexOutOfBoundsException();
}
if ((protectionDomain == null) && !allowNullProtectionDomain) {
protectionDomain = getDefaultProtectionDomain();
}
final ProtectionDomain pd = protectionDomain;
Class<?> answer = defineClassImpl(className, classRep, offset, length, pd);
if (certs != null) {
setSigners(answer, certs);
}
boolean isVerbose = isVerboseImpl();
URL url = null;
if (isVerbose) {
if (pd != null) {
CodeSource cs = pd.getCodeSource();
if (cs != null) {
url = cs.getLocation();
}
}
}
if (isVerbose) {
String location = (url != null) ? url.toString() : "<unknown>"; //$NON-NLS-1$
com.ibm.oti.vm.VM.dumpString("class load: " + answer.getName() + " from: " + location + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return answer;
}
看似defineClassInternal方法代码挺多,其实并没有对参数做过多处理,主要是对参数的合法性判断,转化成合适的格式,最后返回一个Class对象answer,长叹一口气,终于返回值中不带调用的方法了,难道到这里就结束了?那ReflectTest.class是什么时候进入JVM的?迷茫
再来看看answer是怎么得到的,defineClassInternal方法中的这句:
Class<?> answer = defineClassImpl(className, classRep, offset, length, pd);
对比下原来自定义加载器MyClassLoader中地这句:
return defineClass(className , classByte , 0 , classByte.length);
发现了什么?!它竟然还在套娃,最初传入的传入的参数,几经轮转,几乎原封不动地又传入到方法defineClassImpl中,好吧,我们继续跳到defineClassImpl看看.
private final native Class<?> defineClassImpl(String className, byte [] classRep, int offset, int length, Object protectionDomain);
defineClassImpl方法还是一层套娃,但到这里就不能再往下点了,因为这里显示是native修饰的方法,是调用其它语言(C/C++)实现,在JDK库中是看不到的.
类加载器的C++实现
那从哪查看源码呢?在网上四处寻找方法,于是下载了openJDK15,里面有虚拟机实现的源码,虚拟机部分在目录的 jdk15-0dabbdfd97e6\src\hotspot 下,研究了一会,又找到了一个文件classLoader.cpp ,这个文件大概率就是加载器的最终实现,然后我打开了它,嘿嘿,你猜怎么着,我看不懂,因为C++还没学好,而且代码也太多,溜了溜了,如果有能力日后再研究.
随便截取的一点点就已经头皮发麻了,这个研究下来估计得好一会了,自己有几斤几两还是清楚的,如果有什么问题欢迎评论吐槽和指点,如果有所研究希望也可以告诉我,感谢!
附上openJDK15下载地址
附上classLoader.cpp路径:jdk15-0dabbdfd97e6\src\hotspot\share\classfile\classLoader.cpp