黑马程序员--05.类加载器--05【自定义类加载器】【自定义类加载器举例】

来源:互联网 发布:ubuntu server配置网络 编辑:程序博客网 时间:2024/06/17 19:03

类加载器--5

自定义类加载器

自定义类加载器举例

----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------

1.    自定义类加载器

1). 类加载过程可操控部分 (回顾)

(1). 类加载过程的五个环节

[1]. 一个类被加载的五个阶段:加载、验证、准备、解析和初始化。

[2]. 只有加载阶段中的环节可以被编程人员进行自定义编码

(2). 类加载的加载阶段中具体可控环节

[1]. JVM并没有对这条规范“从哪里获取此类的二进制流”并且“怎样获取此类的二进制流”做出明确的规定

[2]. 设计JVM的团队将“通过全类名获取定义此类的二进制字节 (byte)这个动作放到JVM外部去实现,以便让应用程序自己决定如何去获取所需要的类

[3]. 实现通过全类名获取定义此类的二进制字节 (byte)这个动作代码的模块称为类加载器。

(3). 加载阶段的代码对应

以上加载阶段的代码主要对应的就是java.lang.ClassLoader中的protectedClass<?> loadClass(String name, boolean resolve)方法                                                    

2). findClass( )和 loadClass( )方法的关系

(1). findClass() 和loadClass() 出现的时机

[1]. loadClass( )方法出现的版本JDK1.0

[2]. findClass( )方法出现在JDK1.2以后

(2). 引入findClass( )方法的目的

[1]. 类加载器的双亲委托机制在JDK1.2引入的

[2]. 为了向前兼容遵循JDK1.2引入的双亲委托机制,这样自定义的类加载器的内容应该写道这个findClass方法中。

[3]. loadClass( )会调用findClass方法。

3). 自定义类加载器

(1). 自定义类加载器的方式

[1]. 继承java.lang.ClassLoader直接覆盖ClassLoader抽象父类中loadClass方法

[2]. 继承java.lang.ClassLoader直接覆盖ClassLoader抽象父类中findClass方法

(2). 两种自定义类加载器的方法的比较

[1]. 如果采取覆盖ClassLoader抽象父类中loadClass方法,就要自己重新编写双亲委托机制,这样势必非常麻烦

[2]. JDK1.2以后不再提倡直接覆盖loadClass方法,而是应当把自己的类加载逻辑写到findClass方法中。在loadClass()方法的逻辑中如果父类加载失败,就会调用到自定义的findClass方法中的内容。这样就能保证自定义的类加载器符合双亲委托机制

(3). 自定义类加载器的步骤

[1]. 新建类继承java.lang.ClassLoader抽象类

[2]. 子类重写ClassLoader类的findClass( )方法【通常做法】

findClass原型protected Class<?> findClass(String name);

[2] 1. 按照自己的逻辑获取字节码数据

[2]2. 将获取到的字节码数据转换为对应的字节数组(byte[])

[2]3. 调用ClassLoaderdefineClass(byte[], int off, int length)方法将字节码对应的byte[]数据作为实参传给defineClass方法并将defineClass构建的Class对象返回。【因为findClass方法要求返回Class对象

(4). 自定义类加载器的位置

[1]. 自定义类加载器本身是java.lang.ClassLoader的直接子类

【注意】类加载器本身之间的“父子”关系并不是通过extends关键字来表达的而是通过继承java.lang.ClassLoader的parent属性指向不同类加载器构建类加载器之间的关系

[2]. 测试自定义类加载器类加载器的结构中的位置

{1}. 自定义类加载器

public class MyClassLoader extends ClassLoader {}

{2}. 测试代码

public class ClassLoaderTest {       publicstatic voidmain(String[] args) {              ClassLoader loader =new MyClassLoader();              while(loader!= null){                     System.out.println(loader);                     loader =loader.getParent();              }       }}

{3}. 打印结果


从打印结果上来看:直接继承的类加载器MyClassLoader的parent属性直接指向了AppClassLoader实例

【疑问】这个MyClassLoader的parent是怎么被指定的呢

分析:由于parent是MyClassLoader从java.lang.ClassLoader类中继承来的一个成员属性,仅仅是在被new之后就能打印出这样的组织关系。推断:一定是在MyClassLoader的无参构造方法中调用的无参父类构造方法【全部隐式调用】指定了这个MyClassLoader的实例的parent属性

[3]. 自定义类加载器系统默认类加载器关联纽带 --- ClassLoader的构造方法

{1}.ClassLoader的无参构造方法

protectedClassLoader() {     this(checkCreateClassLoader(),getSystemClassLoader());}

这个方法又调用了另一个重载的构造方法,传入的第二个参数是 getSystemClassLoader()的返回值。这个方法的返回值就是系统类加载器,就是AppClassLoader类加载器

{2}.ClassLoader的有参构造方法

privateClassLoader(Void unused, ClassLoader parent) {    this.parent= parent;//…}

结论自定义类加载器new的过程中会自动调用父类ClassLoader的构造方法。父类的这个构造方法会调用另一个重载的构造方法,这个构造方法将自定义的类加载器的parent属性直接指向了AppClassLoader类加载器

所以一般情况下定义的类加载器实例的“父类加载器”【指的是parent指向的实例】都是AppClassLoader

【这样即使使用自定义的类加载器加载类的时候,也一定遵循类加载器的双亲委托机制】

2.    自定义类加载器举例

1). 在所有默认类加载器管辖范围之外放置一个字节码文件

(1). 自定义代码 -----放置到默认类加载器管辖范围之外

[1]. OutClass源代码

public class OuterClass{  publicvoid showOuter(){    System.out.println("Show Outer...");}

[2]. 对OutClass.class打成out.jar包+OutClass.class本身 一同放置到工程的lib文件夹下,如图:


[3]. 将out.jar引入工程ClassLoader中:


【这样所有的默认类加载器无法自动所寻到“D:\BlackHorse_Advanced\ClassLoader\lib”下面的class文件】

2). 测试 + 自定义类加载器

(1). OuterClass测试代码1 ----传统方式

import userDefinedClasses.OuterClass; public static void loadWithoutLoader(){    OuterClassouterClass =new OuterClass();    outerClass.showOuter();}

main:

loadWithoutLoader();

[1]. 编译正确

[2]. 运行抛出异常


【分析】

当前线程类加载器是AppClassLoader并采用委托机制去搜索指定的类,但是没有找到OuterClass字节码。所以抛出ClassNotFoundException异常

(2). OuterClass测试代码2 ----反射方式

public static void loadWithoutLoaderII() throws ClassNotFoundException,InstantiationException, IllegalAccessException, NoSuchMethodException,SecurityException, IllegalArgumentException, InvocationTargetException{    Classclazz =Class.forName("userDefinedClasses.OuterClass");    Objectobj =clazz.newInstance();          Methodmethod =clazz.getMethod("showOuter", null);    method.invoke(obj,null);}

[1]. 编译同样正确

[2]. 运行抛出异常


【分析】

采用Class.forName方法并传入"userDefinedClasses.OuterClass"但是由于当前线程的类加载器是AppClassLoader,所以还是没有办法根据这个字符串找到OuterClass的字节码。所以抛出异常

(3). 解决办法

由于系统的默认类加载器不能搜索到特定位置的,所以必须重新自定义类加载器对这个指定目录的类进行加载。通过loadClass方法加载之后会获取这个OuterClass类的Class对象。这样就可以通过反射来操作这个OuterClass类的对象

(4) 自定义类加载器加载指定目录的字节码文件

[1]. 自定义类加载器MyClassLoader

import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException; public class MyClassLoader extendsClassLoader {     @Override    protected Class<?> findClass(Stringname) throws ClassNotFoundException {       try {           FileInputStreamfis =new FileInputStream(new File(name +".class"));           byte[] classBytes =new byte[1024];                     int length =fis.read(classBytes, 0, classBytes.length);           return defineClass(classBytes, 0,length);       }catch (FileNotFoundException e) {           e.printStackTrace();       }catch (IOException e) {           e.printStackTrace();       }       return null;    } }

[2]. 通过MyClassLoader加载D:\BlackHorse_Advanced\ClassLoader\lib下面的OuterClass

public static void loadWithMyClassLoader(StringclassName, ClassLoader classLoader) throws ClassNotFoundException,InstantiationException, IllegalAccessException, NoSuchMethodException,SecurityException, IllegalArgumentException, InvocationTargetException{    Classclazz =classLoader.loadClass("D:\\BlackHorse_Advanced\\ClassLoader\\lib\\"+ className);    Objectobj =clazz.newInstance();       Methodmethod =clazz.getMethod("showOuter", null);    method.invoke(obj,null);}

main中:

ClassLoader myLoader =new MyClassLoader();String className ="OuterClass";loadWithMyClassLoader(className, myLoader);

[3]. 编译通过,打印结果如下

Show Outer... 

3). 总结

(1). 自定义类加载器需要显式调用loadClass方法

由于自定义类加载器并不是系统类加载器的一部分,所以必须显式调用loadClass才能加载指定的类。

总结】无论是默认类加载器隐式调用loadClass加载各自范围的类

还是自定义类加载器显式调用loadClass加载指定位置的类,目的只有一个,将这个类的字节码文件变成内存中的Class对象

(2). 获取Class类对象的第四种方式

自定义类加载器实例化之后,调用这个类加载器的loadClass方法。这样就可以直接获取这个指定位置【未必在默认类加载器的目录下】的类。

----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------

 

原创粉丝点击