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。

类图如下:

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:上下文加载器原理和案例

Java类加载器(死磕5)的更多相关文章

  1. Java类加载器(死磕 1-2)

      Java类加载器(  CLassLoader ) 死磕 1.2:  导入 & 类加载器分类 本小节目录 1.导入 1.1. 从class文件的载入开始 1.2. 什么是类加载器 2. JA ...

  2. Java类加载器( 死磕9)

    [正文]Java类加载器(  CLassLoader ) 死磕9:  上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...

  3. Java类加载器( 死磕7)

    [正文]Java类加载器(  CLassLoader )死磕7:  基于加密的自定义网络加载器 本小节目录 7.1. 加密传输Server端的源码 7.2. 加密传输Client端的源码 7.3. 使 ...

  4. Java类加载器( 死磕8)

    [正文]Java类加载器(  CLassLoader ) 死磕 8:  使用ASM,和类加载器实现AOP 本小节目录 8.1. ASM字节码操作框架简介 8.2. ASM和访问者模式 8.3. 用于增 ...

  5. Java类加载器( 死磕 6)

    [正文]Java类加载器(  CLassLoader )死磕 6:  自定义网络类加载器 本小节目录 6.1. 自定义网络类加载器的类设计 6.2. 文件传输Server端的源码 6.3. 文件传输C ...

  6. Java类加载器( 死磕 4)

    [正文]Java类加载器(  CLassLoader ) 死磕 之4:  神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. ...

  7. Java类加载器(死磕3)

    [正文]Java类加载器(  CLassLoader ) 死磕3:  揭秘 ClassLoader抽象基类 本小节目录 3.1. 类的加载分类:隐式加载和显示加载 3.2. 加载一个类的五步工作 3. ...

  8. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  9. java类加载器深入研究

    看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析      深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLo ...

随机推荐

  1. xml文档绑定某个属性值到treeview算法

    原文发布时间为:2008-08-10 -- 来源于本人的百度文章 [由搬家工具导入] using System.Xml; protected void Button2_Click(object sen ...

  2. 【leetcode】 First Missing Positive

    [LeetCode]First Missing Positive Given an unsorted integer array, find the first missing positive in ...

  3. 首次远程安装 GlassFish 后以远程 Web 方式访问其后台管理系统出现错误的解决方法(修订)

    首次远程安装 GlassFish 服务后,如果以远程 Web 方式访问其后台管理系统,会提示 Secure Admin must be enabled to access the DAS remote ...

  4. 常用函数和STL

    #include <bits/stdc++.h> using namespace std; #define PI acos(-1.0) int main() { printf(" ...

  5. Django学习笔记(12)——分页功能

    这一篇博客记录一下自己学习Django中分页功能的笔记.分页功能在每个网站都是必要的,当页面因需要展示的数据条目过多,导致无法全部显示,这时候就需要采用分页的形式进行展示. 分页在网站随处可见,下面展 ...

  6. const T、const T*、T *const、const T&、const T*& 的区别

    原文地址: http://blog.csdn.net/luoweifu/article/details/45600415 这里的T指的是一种数据类型,可以是int.long.doule等基本数据类型, ...

  7. luogu P1057 传球游戏

    题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同 ...

  8. 文艺平衡树(Splay)

    题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1, ...

  9. java反射原理运用

    1.首先用Java反射机制的要做到的一个目的:我们都知道通过得到一个对象中的指定方法或者属性等,基于这个原理我们来做一个 通用的功能,让客户端可以通过传入的对象和一个标识去调用这个对象里自己想要的方法 ...

  10. javascript好文---深入理解定位父级offsetParent及偏移大小

    前面的话 偏移量(offset dimension)是javascript中的一个重要的概念.涉及到偏移量的主要是offsetLeft.offsetTop.offsetHeight.offsetWid ...