• 自定义类加载器


    1. 双亲委派模型

    1.1 什么是双亲委派模型?

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

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

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

    扩展类加载器(Extension ClassLoader):负责加载libext目录或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类型。


    1.3 如何实现双亲委派模型?

    双亲委派模型的原理很简单,实现也简单。每次通过先委托父类加载器加载,当父类加载器无法加载时,再自己加载。其实ClassLoader类默认的loadClass方法已经帮我们写好了,我们无需去写。


    1.4 破坏双亲委派模型?

    来自<深入理解JVM虚拟机>

    1.第一次破坏
    由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。在此之前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法唯一逻辑就是去调用自己的loadClass()。
    
    2.第二次破坏
    双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的同一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美。
    
    如果基础类又要调用回用户的代码,那该么办?
    一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,
    它的代码由启动类加载器去加载(在JDK1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者的代码,但启动类加载器不可能“认识”这些代码。
    
    为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
    有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
    
    3.第三次破坏
    双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换、模块热部署等,简答的说就是机器不用重启,只要部署上就能用。
    OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi幻境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当受到类加载请求时,OSGi将按照下面的顺序进行类搜索:
    1)将java.*开头的类委派给父类加载器加载。
    2)否则,将委派列表名单内的类委派给父类加载器加载。
    3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
    4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
    5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
    6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
    7)否则,类加载器失败。
    
    

    2. 自定义类加载器

    2. 1几个重要函数

    2.1.1 loadClass

    loadClass默认实现如下:

    public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
    }

    再看看loadClass(String name, boolean resolve)函数:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
    
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
    
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    • 首先通过Class c = findLoadedClass(name);判断一个类是否已经被加载过。
    • 如果没有被加载过执行if (c == null)中的程序,遵循双亲委派的模型,首先会通过递归从父加载器开始找,直到父类加载器是Bootstrap ClassLoader为止。
    • 最后根据resolve的值,判断这个class是否需要解析。

    可以看出,抽象类ClassLoader的findClass函数默认是抛出异常的。而前面我们知道,loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass函数,因此我们必须要在loadClass这个函数里面实现将一个指定类名称转换为Class对象.

    2.2实例

    2.2.1 待加载类

    定义一个待加载的java类:,放在D:/盘下
    (注意此时没有写pacage XXX;)

    public class TestClassLoader {
        public void hello() {
            System.out.println("我是由" + getClass().getClassLoader().getClass()+"加载进来的!");
        }
    }
    

    2.2.2 此定义类加载器:

    
    public class CustomerClassLoader extends ClassLoader {
        private String classPath;
    
        public CustomerClassLoader(String classPath) {
            super();
            this.classPath = classPath;
        }
    
        // 重写
        protected Class<?> findClass(String className) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(className);
    
                return defineClass(className, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    
        private byte[] loadByte(String className) {
            try {
                File file = new File(classPath + "/" + className + ".class");
                FileInputStream fis = new FileInputStream(file);
    
                int available = fis.available();
                byte[] data = new byte[available];
                fis.read(data);
                fis.close();
                return data;
    
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) throws Exception {
            CustomerClassLoader customerClassLoader = new CustomerClassLoader("D:/");
            Class<?> loadClass = customerClassLoader.loadClass("TestClassLoader");
            Object newInstance = loadClass.newInstance();
            Method method = loadClass.getMethod("hello", null);
            method.invoke(newInstance, null);
    
        }
    }

    2.3.1 待加载的类2

    package classLoader;
    public class TestClassLoader_pack {
        public void hello() {
            System.out.println("我是由" + getClass().getClassLoader().getClass()+"加载进来的!");
        }
    }
    

    自定义类加载器

    
    public class CustomerClassLoader extends ClassLoader {
        private String classPath;
    
        public CustomerClassLoader(String classPath) {
            super();
            this.classPath = classPath;
        }
    
        // 重写
        protected Class<?> findClass(String className) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(className);
                //如果待加载的class的java源文件第一行有package XXX的话,需要再做处理 
                return defineClass("classLoader.TestClassLoader_pack", data, 0, data.length);
                //return defineClass(className, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    
        private byte[] loadByte(String className) {
            try {
                File file = new File(classPath + "/" + className + ".class");
                FileInputStream fis = new FileInputStream(file);
    
                int available = fis.available();
                byte[] data = new byte[available];
                fis.read(data);
                fis.close();
                return data;
    
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) throws Exception {
            CustomerClassLoader customerClassLoader = new CustomerClassLoader("D:/");
            //Class<?> loadClass = customerClassLoader.loadClass("TestClassLoader");
            Class<?> loadClass = customerClassLoader.loadClass("TestClassLoader_pack");
            Object newInstance = loadClass.newInstance();
            Method method = loadClass.getMethod("hello", null);
            method.invoke(newInstance, null);
    
        }
    }

    参考:
    https://www.cnblogs.com/wxd0108/p/6681618.html
    https://blog.csdn.net/jearbilove/article/details/25538819

  • 相关阅读:
    强行删除带点的文件夹
    如何设置让iis服务器支持.apk文件的下载
    纯CSS下拉导航菜单
    <UL>中<li>标签前编号图片的简单调用
    滑动门效果【鼠标滑过鼠标单击两种】
    SQL Server中,NumricDecimalMoney三种字段类型的区别
    SQL Server 20个最常用的时间格式
    Gridview------Set BackgroundColor
    c# 中is 和 as 运算符
    SQL LEFT JOIN 关键字 SQL RIGHT JOIN 关键字 fulljoin
  • 原文地址:https://www.cnblogs.com/DiZhang/p/12544964.html
Copyright © 2020-2023  润新知