• Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论


    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    创建用户自定义的类加载器

      要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数指定的类的名字,返回对应的Class对象的引用。

    自定义类加载器的例子

      代码:

    复制代码
    package com.mengdd.classloader; 
    import java.io.ByteArrayOutputStream;
    import
    java.io.File;
    import
    java.io.FileInputStream;
    import java.io.InputStream;

    public
    class MyClassLoader extends ClassLoader {
    private String name; // 类加载器的名字
    private String path = "d:\"; // 加载类的路径
    private final String fileType = ".class"; // class文件的扩展名

    public
    MyClassLoader(String name) {
    super
    (); // 让系统类加载器成为该类加载器的父加载器
    this
    .name = name;
    }

    public
    MyClassLoader(ClassLoader parent, String name) {
    super(parent); // 显式指定该类加载器的父加载器
    this
    .name = name;
    } @Override


    public
    String toString() {
    return
    this.name;
    }
    public String getPath() {
    return
    path;
    }
    public void setPath(String path) {
    this.path = path;
    }
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
    // 重写的时候把protected改为public
    /
    获取字节数组
    byte[] data = this.loadClassData(name);
    // 将字节数组转换成Class对象返回
    return this.defineClass(name, data, 0, data.length);
    }
    /**
    * 得到class文件的二进制字节数组
    *
    *
    @param name
    *
    @return
    */

    private byte[] loadClassData(String name) {
    InputStream is
    = null;
    byte[] data = null;
    ByteArrayOutputStream baos
    = null;
    try {
    // 将完整类名中的.转化成
    name = name.replace(".", "\");
    is = new FileInputStream(new File(path + name + fileType));
    baos = new ByteArrayOutputStream();
    int ch = 0;
    while (-1 != (ch = is.read())) {
    baos.write(ch);
    }
    data = baos.toByteArray();
    }catch (Exception e) {
    e.printStackTrace();
    }
    finally {
    try {
    is.close();
    baos.close();
    }
    catch (Exception e2) {

    }
    }
    return data;
    }

    // main方法用来测试
    public static void main(String[] args) throws Exception {
    MyClassLoader loader1
    = new MyClassLoader("loader1");
    // loader1的父加载器是系统类加载器
    // 系统类加载器会在classpath指定的目录中加载类
    loader1.setPath("d:\myapp\serverlib\");
    MyClassLoader loader2
    = new MyClassLoader(loader1, "loader2");
    // 将loader1作为loader2的父加载器
    loader2.setPath("d:\myapp\clientlib\");
    MyClassLoader loader3
    = new MyClassLoader(null, "loader3");
    // loader3的父加载器是根类加载器
    loader3.setPath("d:\myapp\otherlib\");
    // 测试加载
    test(loader2);
    test(loader3);
    System.out.println(
    "test2---------------");
    //
    测试不同命名空间的类的互相访问
    test2(loader3);
    }

    public static void test(ClassLoader loader) throws Exception {
    Class clazz
    = loader.loadClass("com.mengdd.classloader.Sample");
    Object object
    = clazz.newInstance();
    }


    public
    static void test2(ClassLoader loader) throws Exception {
    Class clazz
    = loader.loadClass("com.mengdd.classloader.Sample");
    Sample object
    = (Sample) clazz.newInstance();
    System.out.println(
    "sample v1: " + object.v1);

    }
     }
    复制代码

      其中Sample:

    复制代码
    package com.mengdd.classloader; 
    public class Sample {
    public
    int v1 = 1;
    public Sample() {
    System.out.println(
    "Sample is loaded by: " + this.getClass().getClassLoader());
    // 主动使用Dog类
    new
    Dog();
    }
    }
    复制代码

      Dog类:

    复制代码
    package com.mengdd.classloader;  
    public class Dog {
    public
    Dog() {
    System.out.println(
    "Dog is loaded by: " + this.getClass().getClassLoader());
    }
    }
    复制代码

      例子演示过程略,尝试把class文件放在不同的路径下,看输出或者报错结果。

      主要结论就是验证了父亲委托机制。

      采用loader1的时候由于其父类是系统类加载器(也即应用类加载器),所以如果可以在classpath中找到目标.class文件,则定义类加载器是系统类加载器,输出类似:

      sun.misc.Launcher$AppClassLoader@7448bc3d

      每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。

     

      在Sample类中主动使用了Dog类,当执行Sample类的构造方法中的new Dog()语句时,Java虚拟机需要先加载Dog类,到底用哪个类加载器加载呢?

      从打印结果可以看出,Java虚拟机会用Sample类的定义类加载器去加载Dog类,加载过程也同样采用父亲委托机制

      如果Sample类首次主动使用Dog时,Sample类的加载器及它的父加载器都无法加载Dog类,将会抛出找不到文件的异常。

    不同类加载器的命名空间关系

      同一个命名空间内的类是相互可见的,即可以互相访问。

      子加载器的命名空间包含所有父加载器的命名空间。

      因此由子加载器加载的类能看见父加载器加载的类。

      例如系统类加载器加载的类能看见根类加载器加载的类。

      由父加载器加载的类不能看见子加载器加载的类。

      可以理解为:由于子加载器中含有父加载器的引用,所以子加载器的范围更大

     

      如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。

      比如这么一种情况:MyClassLoader类由系统类加载器加载,而Sample类由loader3类加载器加载,因此MyClassLoader类看不见Sample类。

      在MyClassLoader类的main()方法中使用Sample类,会导致错误。

      当两个不同命名空间内的类互相不可见时,可采用Java反射机制来访问对方实例的属性和方法,即反射可以突破命名空间的限制。

    参考资料

      圣思园张龙老师Java SE视频教程。

      ClassLoader类:http://docs.oracle.com/javase/7/docs/api/

      相关博文:

      Java虚拟机JVM学习05 类加载器的父委托机制:http://www.cnblogs.com/mengdd/p/3562540.html

     博文源于:

     

  • 相关阅读:
    《JAVA设计模式》中的代理模式Proxy
    《JAVA设计模式》中的外观模式Facade
    《JAVA设计模式》中的组合模式Composite
    《JAVA设计模式》中的享元模式Flyweight
    《JAVA设计模式》中的策略模式Strategy
    《JAVA设计模式》中的模板模式Template
    《JAVA设计模式》中的观察者模式Observer
    《JAVA设计模式》中的迭代器模式Iterator
    《JAVA设计模式》中的责任链模式Chain of Responsibility
    面对对象编程
  • 原文地址:https://www.cnblogs.com/hqlong/p/6814408.html
Copyright © 2020-2023  润新知