当前位置: 代码迷 >> 综合 >> Java---类反射---ClassLoader(类加载器)
  详细解决方案

Java---类反射---ClassLoader(类加载器)

热度:91   发布时间:2023-10-21 17:25:02.0

    Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:

    BootStrap,  ExtClassLoader,  AppClassLoader

    类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap

    Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。

Java---类反射---ClassLoader(类加载器)

类加载器的委托机制

    当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

    首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。

    还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

    每个类加载器加载类时,又先委托给其上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?

    对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的person.jar包中后,运行结果为ExtClassLoader的原因。

代码演示:

使用到的Person类:

package cn.hncu.javaSE.classLoader.v1;public class Person{private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person [ " + name + ", " + age + " ]";}}

演示:

package cn.hncu.javaSE.classLoader.v1;import org.junit.Test;public class ClassLoaderDemo {@Test//认识三个系统类加载器public void t1() {/** 每个类加载器加载类时,又先委托给其上级类加载器。* 当所有祖宗类加载器没有加载到类,回到发起者类加载器,* 还加载不了,则抛ClassNotFoundException。* 	BootStrap 是 ExtClassLoader 父类(上级类)* 	ExtClassLoader 是 AppClassLoader 的父类(上级类)*///AppClassLoader 应用程序类加载器  加载 classpath指定的所有jar或目录ClassLoader classLoader = Person.class.getClassLoader();System.out.println( "111>>>" + classLoader.toString() );//ExtClassLoader 扩展类加载器 加载JRE/lib/ext/*.jarclassLoader = classLoader.getParent();System.out.println( "222>>>" + classLoader.toString() );//BootStrap 不是java类 所以为 null 加载 JRE/lib/rt.jar classLoader = classLoader.getParent();System.out.println( "333>>>" + classLoader );//我们 定义的 类 默认都是 通过 AppClassLoader 加载的//接下来 看看 String 的加载类是谁ClassLoader classLoader2 = String.class.getClassLoader();System.out.println( classLoader2 ); //null// null -> BootStrap 最原始的 类加载器。 // 通过观察 可以发现 String 是处于 JRE/lib/rt.jar 中的 // 所以,验证了 父类委托机制   BootStrap加载了 其子类或者孙类 就不加载了}/*演示父类委托机制: 如果把Person.class打包成person.jar,并把它放在"JRE/lib/ext/"目录下,则无论在当前项目中如何修改person类,运行时都不会改变。*/@Testpublic void t2() {Person p = new Person( "Tom",18 );System.out.println( p );}
}

Java---类反射---ClassLoader(类加载器)

Java---类反射---ClassLoader(类加载器)

person.jar放置的位置 要与项目配置的运行环境(jre/jdk)要对应起来,否则没用

这样放置person.jar然后再去修改当前项目中的person类的toString方法最后再运行 t2() 测试用例 并观察。

自定义类加载器:

    虚拟机的核心是通过类加载器来加载.class文件,然后进行相应的解析执行。那么我们可以自己做类加载器,手动加载需要的.class以进行解析执行,从而扩展虚拟机的功能。
    以下内容摘自API文档:

        应用程序需要实现 ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。

        网络类加载器子类必须定义方法 findClass 和 loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法 defineClass 来创建类实例。

package cn.hncu.javaSE.classLoader.v2;import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;/*** 2018年5月20日 下午5:53:19* @author <a href="mailto:447441478@qq.com">宋进宇</a><br/>*	制作我们的类加载器,可以加载 特殊路径*/
public class MyClassLoader extends ClassLoader {/*** 通过class文件的绝对路径加载该类到运行环境中* @param name class文件的绝对路径* @return 该class文件所对应的 类模板对象* @throws IOException */public Class<?> MyFineClass(String name) throws IOException {FileInputStream fin = new FileInputStream( name );byte[] buf = new byte[1024];int len = fin.read( buf );//不知道 文件内容 大小 采用 内存流  ByteArrayOutputStreamByteArrayOutputStream baos = new ByteArrayOutputStream();while ( len != -1 ) {baos.write( buf, 0, len);len = fin.read( buf );}fin.close(); //关流baos.close(); //关流 并刷缓存byte[] b = baos.toByteArray();//通过字节数组 加载类Class<?> cls = defineClass( null, b, 0, b.length);return cls;}public static void main(String[] args) throws Exception{MyClassLoader myLoader = new MyClassLoader();//这个路径是class文件存放的绝对路径Class<?> cls = myLoader.MyFineClass( "D:\\Myjava\\MyEclipse2016\\练习\\bin\\cn\\hncu\\javaSE\\classLoader\\v1\\Person.class" );Object obj = cls.newInstance();System.out.println( obj ); //AC// 调用setter 给 obj 赋值/*Person p = (Person) obj; //WAp.setName( "张三" );p.setAge( 20 );System.out.println( p );上面这样 强转 是错误的,强转 只是 让编译时多态过,运行时多态 还是看内存的Person是通过AppClassLoader 加载的 ,obj是通过 MyClassLoader加载的两个类模板加载的 空间 不同 即 内存是不一样的 所以 即使可以强转 ,且就是同一个class文件但是运行时是过不了的,类强转异常。*///实现上面功能如下Method m1 = cls.getDeclaredMethod( "setName", String.class );m1.invoke( obj, "张三" );Method m2 = cls.getDeclaredMethod( "setAge", int.class );m2.invoke( obj, 20 );System.out.println( obj ); //AC}
}


  相关解决方案