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。
类图如下:
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群
无编程不创客,无案例不学习。 一定记得去跑一跑案例哦
类加载器 系列 全目录
8. 高级案例1:使用ASM技术,结合类加载器,解密AOP原理
Java类加载器(死磕5)的更多相关文章
- Java类加载器(死磕 1-2)
Java类加载器( CLassLoader ) 死磕 1.2: 导入 & 类加载器分类 本小节目录 1.导入 1.1. 从class文件的载入开始 1.2. 什么是类加载器 2. JA ...
- Java类加载器( 死磕9)
[正文]Java类加载器( CLassLoader ) 死磕9: 上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...
- Java类加载器( 死磕7)
[正文]Java类加载器( CLassLoader )死磕7: 基于加密的自定义网络加载器 本小节目录 7.1. 加密传输Server端的源码 7.2. 加密传输Client端的源码 7.3. 使 ...
- Java类加载器( 死磕8)
[正文]Java类加载器( CLassLoader ) 死磕 8: 使用ASM,和类加载器实现AOP 本小节目录 8.1. ASM字节码操作框架简介 8.2. ASM和访问者模式 8.3. 用于增 ...
- Java类加载器( 死磕 6)
[正文]Java类加载器( CLassLoader )死磕 6: 自定义网络类加载器 本小节目录 6.1. 自定义网络类加载器的类设计 6.2. 文件传输Server端的源码 6.3. 文件传输C ...
- Java类加载器( 死磕 4)
[正文]Java类加载器( CLassLoader ) 死磕 之4: 神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. ...
- Java类加载器(死磕3)
[正文]Java类加载器( CLassLoader ) 死磕3: 揭秘 ClassLoader抽象基类 本小节目录 3.1. 类的加载分类:隐式加载和显示加载 3.2. 加载一个类的五步工作 3. ...
- java笔记--理解java类加载器以及ClassLoader类
类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...
- java类加载器深入研究
看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析 深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLo ...
随机推荐
- 线程间通过PostMessage通信
1.查看TMS项目中的相关实例 ::PostMessage(hWnd, WM_USER_MSG_REFRESH_UI, (WPARAM)UMP_REFRESH_MEMBER_INFO, 0); 参考文 ...
- 在4418平台上如何配置GPIO口的状态
硬件 ------------------------------------------------------------------------------------------------- ...
- LeetCode OJ--Search Insert Position
https://oj.leetcode.com/problems/search-insert-position/ 数组有序,给一个数,看它是否在数组内,如果是则返回位置,如果不在则返回插入位置. 因为 ...
- window.getComputedStyle——ref
componentDidMount() { const LeftHeight = window.getComputedStyle(this.leftDom).height; console.log(L ...
- (2)html 块类
span span是内联元素,内联元素的特点:在显示时通常不会以新行开始 div div是块级元素,块级元素会换新行 class 设置 <head> <style> .citi ...
- Remove Nth Node From End of List(链表,带测试代码)
Given a linked list, remove the nth node from the end of list and return its head. For example, Give ...
- codevs贪吃的九头龙
传说中的九头龙是一种特别贪吃的动物.虽然名字叫“九头龙”,但这只是说它出生的时候有九个头,而在成长的过程中,它有时会长出很多的新头,头的总数会远大于九,当然也会有旧头因衰老而自己脱落.有一天,有M 个 ...
- JavaScript的变量:变量提升
JavaScript代码的运行规则 在JavaScript代码运行之前其实是有一个编译阶段的.编译之后才是从上到下,一行一行解释执行.这样一来也给初学者造成很大的误解.初学者会觉得JavaScript ...
- python2.7升python3.2
1. 安装python3.2 sudo apt-get install python3.2 2. 删除usr/bin/目录下的默认python link文件. cd /usr/bin sudo ...
- GO --微服务框架(一) goa
当项目逐渐变大之后,服务增多,开发人员增加,单纯的使用go来写服务会遇到风格不统一,开发效率上的问题. 之前研究go的微服务架构go-kit最让人头疼的就是定义服务之后,还要写很多重复的框架代码,一直 ...