美文网首页
java 详解类加载器的双亲委派及打破双亲委派

java 详解类加载器的双亲委派及打破双亲委派

作者: 青城楼主 | 来源:发表于2018-08-01 09:42 被阅读134次

    java 详解类加载器的双亲委派及打破双亲委派

    https://blog.csdn.net/Dopamy_BusyMonkey/article/details/79739748

    首先,先要知道什么是类加载器。简单说,类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。如果站在JVM的角度来看,只存在两种类加载器:

    启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。

    其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:

    扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
    应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
    双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

    类加载器的双亲委派模型
    1.2 为什么需要双亲委派模型?

    为什么需要双亲委派模型呢?假设没有双亲委派模型,试想一个场景:

    黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。

    而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

    或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗?确实,这样是可行。但是,在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。

    举个简单例子:

    ClassLoader1、ClassLoader2都加载java.lang.String类,对应Class1、Class2对象。那么Class1对象不属于ClassLoad2对象加载的java.lang.String类型。

    二、打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法,如下例子:

    ①定义Test类。

    ?
    1
    2
    3
    4
    5
    public class Test {
    public Test(){
    System.out.println(this.getClass().getClassLoader().toString());
    }
    }
    ②重新定义一个继承ClassLoader的TestClassLoaderN类,这个类与前面的TestClassLoader类很相似,但它除了重写findClass方法外还重写了loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法,即这里先尝试交由System类加载器加载,加载失败才会由自己加载。它并没有优先交给父类加载器,这就打破了双亲委派机制。

    public class TestClassLoaderN extends ClassLoader {

    private String name;

    public TestClassLoaderN(ClassLoader parent, String name) {
    super(parent);
    this.name = name;
    }

    @Override
    public String toString() {
    return this.name;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    Class<?> clazz = null;
    ClassLoader system = getSystemClassLoader();
    try {
    clazz = system.loadClass(name);
    } catch (Exception e) {
    // ignore
    }
    if (clazz != null)
    return clazz;
    clazz = findClass(name);
    return clazz;
    }

    @Override
    public Class<?> findClass(String name) {

    InputStream is = null;
    byte[] data = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
      is = new FileInputStream(new File("d:/Test.class"));
      int c = 0;
      while (-1 != (c = is.read())) {
        baos.write(c);
      }
      data = baos.toByteArray();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        is.close();
        baos.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return this.defineClass(name, data, 0, data.length);
    

    }

    public static void main(String[] args) {
    TestClassLoaderN loader = new TestClassLoaderN(
    TestClassLoaderN.class.getClassLoader(), "TestLoaderN");
    Class clazz;
    try {
    clazz = loader.loadClass("test.classloader.Test");
    Object object = clazz.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }

    1.2 为什么需要双亲委派模型?

    为什么需要双亲委派模型呢?假设没有双亲委派模型,试想一个场景:

    黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。

    而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

    或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗?确实,这样是可行。但是,在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。

    举个简单例子:

    ClassLoader1、ClassLoader2都加载java.lang.String类,对应Class1、Class2对象。那么Class1对象不属于ClassLoad2对象加载的java.lang.String类型。

    二、打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法,如下例子:

    ①定义Test类。

    ?
    1
    2
    3
    4
    5
    public class Test {
    public Test(){
    System.out.println(this.getClass().getClassLoader().toString());
    }
    }
    ②重新定义一个继承ClassLoader的TestClassLoaderN类,这个类与前面的TestClassLoader类很相似,但它除了重写findClass方法外还重写了loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法,即这里先尝试交由System类加载器加载,加载失败才会由自己加载。它并没有优先交给父类加载器,这就打破了双亲委派机制。

    public class TestClassLoaderN extends ClassLoader {

    private String name;

    public TestClassLoaderN(ClassLoader parent, String name) {
    super(parent);
    this.name = name;
    }

    @Override
    public String toString() {
    return this.name;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    Class<?> clazz = null;
    ClassLoader system = getSystemClassLoader();
    try {
    clazz = system.loadClass(name);
    } catch (Exception e) {
    // ignore
    }
    if (clazz != null)
    return clazz;
    clazz = findClass(name);
    return clazz;
    }

    @Override
    public Class<?> findClass(String name) {

    InputStream is = null;
    byte[] data = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
      is = new FileInputStream(new File("d:/Test.class"));
      int c = 0;
      while (-1 != (c = is.read())) {
        baos.write(c);
      }
      data = baos.toByteArray();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        is.close();
        baos.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return this.defineClass(name, data, 0, data.length);
    

    }

    public static void main(String[] args) {
    TestClassLoaderN loader = new TestClassLoaderN(
    TestClassLoaderN.class.getClassLoader(), "TestLoaderN");
    Class clazz;
    try {
    clazz = loader.loadClass("test.classloader.Test");
    Object object = clazz.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }

    相关文章

      网友评论

          本文标题:java 详解类加载器的双亲委派及打破双亲委派

          本文链接:https://www.haomeiwen.com/subject/qdycvftx.html