• Java类加载器(死磕5)


    Java类加载器(  CLassLoader )  死磕5: 

    自定义一个文件系统classLoader

    本小节目录


    5.1. 自定义类加载器的基本流程
    5.2. 入门案例:自定义文件系统类加载器
    5.3. 案例的环境配置
    5.4 FileClassLoader 案例实现步骤
    5.5. FileClassLoader 的类设计
    5.6. FileClassLoader 的源码
    5.7. FileClassLoader 的使用
    5.8. 不同类加载器的命名空间关系
    5.9. 自定义加载器的两个要点


    1.1. 自定义classLoader


    不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。

    要实现其他的途径的类加载,比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,就需要我们自定义一个classloader。 


    1.1.1. 自定义类加载器的基本流程


    实际上,除了和本地实现密切相关的Bootstrap启动类加载器之外,包括Extention标准扩展类加载器和AppClassLoader应用类加载器在内的所有其他类加载器,都可以当做自定义类加载器来对待。

    前面的内容中已经对java.lang.ClassLoader抽象类中的loadClass方法做了介绍,在此方法中,如果所有的上层类加载器都没有加载成功,则调用本类加载器的findClass()方法,在自己的地盘,获取对应的字节码,并完成字节码到类的转变,并且将class加载到缓存中。这个findClass()方法,就是自定义加载器的关键。

    实现一个自定义加载器,总体来说,分成三步:

    (1)在自己的地盘,获取对应的字节码;

    (2)并完成字节码到类的转变;

    (3)将class加载到缓存中;

    前面两步,需要在findClass()方法中完成。

    废话少说,先看一个简单实例。


    1.1.2. 入门案例:自定义文件系统类加载器


    在宠物店的案例中,如果需要装载第三方的宠物库,并且第三方宠物库的类路径非常灵活。

    现在需要设计自定义加载器,按照需要,从第三方库的加载宠物到内存。这里设计一个自定义加载类FileClassLoader。

    在设计FileClassLoader之前,先交代一下第三方的宠物类LittleDog ,为了演示,和之前的Dog类代码99%相同的。

    LittleDog 的代码如下:

    package com.crazymakercircle.annoDemo;
    
    .......
    
    public class LittleDog implements IPet{
    
        //宠物编号
    
        private static int dogNo;
    
        protected String name;
    
        protected int age;
    
        //无参构造器
    
        public LittleDog() {
    
            dogNo++;
    
            name="LittleDog-"+ dogNo;
    
            age = RandomUtil.randInMod(20);
    
        }
    
        @Tanscation
    
        public LittleDog sayHello() {
    
            Logger.info("嗨,大家好!我是" + name);
    
            return this;
    
        }
    
        @Tanscation
    
        public LittleDog sayAge() {
    
            Logger.info("我是" + name + ",我的年龄是:" + age);
    
            return this;
    
        }
    
    }
    

    至此,一个简单的第三方类——LittleDog宠物类,已经介绍完毕。

    下面看看本案例所涉及到的路径,和其他的环境配置。


    1.1.3. 案例的环境配置


    这个类的名字,在System.properties 配置文件的配置项为:

    pet.dog.class=com.crazymakercircle.otherPet.pet.LittleDog

    编译完成之后,存在在一个独立的路径中。

    这个第三方类库的路径,为了可以灵活多变,并且与源代码工程的输出路径相不能相同,也在System.properties 配置文件增加配置项,具体为:

    class.server.path=D:/疯狂创客圈 死磕java/code/out2/

    为这方便读取,给这个两个配置项,增加其在对应在SystemConfig 配置类中的常量,具体如下:

    package com.crazymakercircle.config;
    
    @ConfigFileAnno(file = "/system.properties")
    
    public class SystemConfig extends ConfigProperties
    
    {
    
    ............
    
        //第三方的类路径
    
        @ConfigFieldAnno(proterty = "class.server.path")
    
        public static String CLASS_SERVER_PATH;
    
        //宠物狗的类型
    
        @ConfigFieldAnno(proterty = "pet.dog.class")
    
        public static String PET_DOG_CLASS;
    
    ............
    
    }
    

    编译完成LittleDog类后,将.class文件,复制到配置项%class.server.path% 所在的目录下。

    至此,环境配置已经交代完毕。

    下面马上进入正题。


    1.1.4. FileClassLoader 案例实现步骤


    自定义类加载器,首先要继承ClassLoader抽象类,并且重写其findClass()方法。

    在重写的findClass()方法中,完成以下三步:

    (1)在自己的地盘(查找路径),获取对应的字节码;

    (2)并完成字节码到Class类对象的转变;

    (3)返回Class类对象。

    接下来,findClass()方法返回Class类对象之后,ClassLoader抽象类的代码,会将新返回Class类对象加载到缓存中,这个工作由抽象类ClassLoader加载器去完成。


    1.1.5. FileClassLoader 的类设计


    这里的自定义加载的名称为——FileClassLoader。

    类图如下:

    wpsEAB4.tmp

    FileClassLoader的成员属性rootDir,用来存着自己的查找路径。当加载一个类时,如果所有的双亲加载器都没有加载到,就去rootDir下查找。

    FileClassLoader重写了的findClass()方法。在这个重写方法中,首先会调用getClassData,在自己的地盘(查找路径),获取对应的字节码,返回字节码的二进制数组。

    FileClassLoader增加了的getClassData()方法,这是其自己的私有方法。主要是找到类的二进制class文件,然后通过读取文件流的方式,读取文件的字节码,供findClass()重写方法使用。


    1.1.6. FileClassLoader 的源码


    简单粗暴,直接上源码。

    package com.crazymakercircle.classLoader;
    
    import java.io.*;
    
    public class FileClassLoader extends ClassLoader {
    
        private String rootDir;
    
        public FileClassLoader(String rootDir) {
    
            this.rootDir = rootDir;
    
        }
    
        public FileClassLoader(ClassLoader parent,String rootDir) {
    
            super(parent);
    
            this.rootDir = rootDir;
    
        }
    
        @Override
    
        protected Class<?> findClass(String name)
    
    throws ClassNotFoundException
    
    {
    
            byte[] classData = getClassData(name);
    
            if (classData == null) {
    
                throw new ClassNotFoundException();
    
            } else {
    
                return defineClass(name, classData, 0, classData.length);
    
            }
    
        }
    
        protected byte[] getClassData(String className)
    
     {
    
            String path = classNameToPath(className);
    
            try {
    
                InputStream ins = new FileInputStream(path);
    
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
                int bufferSize = 4096;
    
                byte[] buffer = new byte[bufferSize];
    
                int bytesNumRead = 0;
    
                while ((bytesNumRead = ins.read(buffer)) != -1) {
    
                    baos.write(buffer, 0, bytesNumRead);
    
                }
    
                return baos.toByteArray();
    
            } catch (IOException e) {
    
                e.printStackTrace();
    
            }
    
            return null;
    
        }
    
        protected String classNameToPath(String className) {
    
            return rootDir + File.separatorChar
    
                    + className.replace('.', File.separatorChar) + ".class";
    
        }
    
    }
    


    案例路径:com.crazymakercircle.classLoader.ClassLoader

    案例提示:无编程不创客、无案例不学习。一定记得看案例哦


    上面的findClass()方法中,了调用defineClass()方法。这个方法是基类ClassLoader的方法,其作用是,将字节码导入到JVM的方法区内存,完成Class类对象加载、验证、准备、解析四步工作。 这个方法在编写自定义class loader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常。


    总结一下,自定义加载器的步骤为:

    (1)编写一个类继承自ClassLoader抽象类。 

    (2)重写它的findClass()方法。

    (3)在自己的地盘,获取对应的字节码;

    (4)调用defineClass()方法,将字节码加载成Class对象,并且返回。


    1.1.7. FileClassLoader 的使用


    简单粗暴,先上代码:

    public class FileLoaderDemo
    
    {
    
        public static void useFileLoader() {
    
            try {
    
                String baseDir = SystemConfig.CLASS_SERVER_PATH;
    
                FileClassLoader fileClassLoader = new FileClassLoader(baseDir);
    
                String className =SystemConfig.PET_DOG_CLASS;
    
                Class dogClass = fileClassLoader.loadClass(className);
    
                Logger.info("显示dogClass的ClassLoader =>");
    
                ClassLoaderUtil.showLoader4Class(dogClass);
    
            } catch (ClassNotFoundException e) {
    
                e.printStackTrace();
    
            }
    
        }
    
        public static void main(String[] args) {
    
            useFileLoader();
    
        }
    
    }
    


    上面的例子中,所加载的类为:

    pet.dog.class=com.crazymakercircle.otherPet.pet.LittleDog

    这个类是提前编译完成之后,放置在在一个独立的路径中,这个路径为:

    class.server.path=D:/疯狂创客圈 死磕java/code/out2/


    需要注意的是,这个路径不包括在当前工程的类路径%java.class.path%中。否则,自定义的加载器,是铁定加载不到的。

    为什么呢?


    依据双亲委托机制,包括在当前工程的类路径%java.class.path%中的类,会优先被AppClassLoader加载。因为自定义加载器的parent,默认就是AppClassLoader。


    案例路径:com.crazymakercircle.classLoaderDemo.base.FileLoaderDemo

    案例提示:无编程不创客、无案例不学习。一定要跑案例哦


    运行的结果是:

      showLoaderTree |>  com.crazymakercircle.classLoader.FileClassLoader@2a18f23c
    
          showLoaderTree |>  sun.misc.Launcher$AppClassLoader@18b4aac2
    
          showLoaderTree |>  sun.misc.Launcher$ExtClassLoader@6fdb1f78
    

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


    同一个命名空间内的类是相互可见的。子加载器的命名空间包含所有父加载器的命名空间。因此子加载器加载的类能看见父加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。

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

    如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。当两个不同命名空间内的类相互不可见时,可以采用Java的反射机制来访问实例的属性和方法。

    这里,需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。

    即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。

    下面看一段代码:

    package com.crazymakercircle.classLoaderDemo.base;
    
    public class LoaderedCompare
    
    {
    
        public static void  showClassSame() {
    
            try {
    
                String baseDir = SystemConfig.CLASS_SERVER_PATH;
    
                FileClassLoader fileClassLoader = new FileClassLoader(baseDir);
    
                String className =SystemConfig.PET_DOG_CLASS;
    
                Class dogClass = fileClassLoader.loadClass(className);
    
                FileClassLoader classLoader2 = new FileClassLoader(baseDir);
    
                Class dogClass2 = classLoader2.loadClass(className);
    
                Logger.info("dogClass2.equals(dogClass) => ");
    
                Logger.info( dogClass2.equals(dogClass));
    
            } catch (ClassNotFoundException e) {
    
                e.printStackTrace();
    
            }
    
        }
    
        public static void main(String[] args) {
    
            showClassSame();
    
        }
    
    }
    


    案例路径:com.crazymakercircle.classLoaderDemo.base.LoaderedCompare

    案例提示:无编程不创客、无案例不学习。一定要跑案例哦


    运行的结果是:

      showClassSame |>  dogClass2.equals(dogClass) =>
    
       showClassSame |>  false
    



    上面的例子中,加载的是同样的字节码文件。甚至两个加载都是同一个类,只是是两个不同的类加载器对象。但是,加载完成之后,在内存中的Class对象,是不一样的。


    1.1.9. 自定义加载器的两个要点


    要点一:

    如果一个自定义加载器创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。 

    为什么呢?

    原因是:如果默认的parent父加载器是AppClassLoader,这样就能够保证它能访问系统内置加载器加载成功的class文件。


    要点二:

    一般尽量不要重写ClassLoader抽象类的loadClass()方法,破坏其中的双亲委托的程序逻辑。

    在JVM规范和JDK文档中(1.2或者以后版本中),都没有建议用户重写loadClass()方法,相比而言,明确提示开发者在开发自定义的类加载器时重写findClass()逻辑。




    源码:


    代码工程:  classLoaderDemo.zip

    下载地址:在疯狂创客圈QQ群文件共享。


    疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 交流编程体验心得
    QQ群链接:
    疯狂创客圈QQ群


    无编程不创客,无案例不学习。 一定记得去跑一跑案例哦


    类加载器 系列  全目录

    1.导入

    2. JAVA类加载器分类

    3. 揭秘ClassLoader抽象基类

    4. 神秘的双亲委托机制

    5. 入门案例:自定义一个文件系统的classLoader

    6. 基础案例:自定义一个网络类加载器

    7. 中级案例:设计一个加密的自定义网络加载器

    8. 高级案例1:使用ASM技术,结合类加载器,解密AOP原理

    9. 高级案例2:上下文加载器原理和案例

  • 相关阅读:
    同一根域名的多站点登录共享
    mysql忘记管理员密码
    使用Cacti监控你的网络(四) Cacti脚本及模板
    使用Cacti时常见的问题集
    SQL Server:SQL Like 通配符特殊用法:Escape
    IS6.0 应用程序池Web园导致Session丢失
    VMware建立虚拟机无法上网
    SqlServer 添加用户 添加角色 分配权限
    教你如何在SQL Server数据库中加密数据
    sendmail邮件服务器搭载smtp和pop3认证的配置方法
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/9832391.html
Copyright © 2020-2023  润新知