为何要花时间实现自己的ClassLoader

尽管人生的乐趣非常大一部分来自于将时间花在有意思可是无意义的事情上,可是这件事绝对是有意思并且有意义的,有下面几个情景是值得我们花费时间实现自己的classLoader的:

  • 我们须要的类不一定存放在已经设置好的classPath下(有系统类载入器AppClassLoader载入的路径),对于自己定义路径中的class类文件的载入,我们须要自己的ClassLoader
  • 有时我们不一定是从类文件里读取类,可能是从网络的输入流中读取类,这就须要做一些加密和解密操作,这就须要自己实现载入类的逻辑,当然其它的特殊处理也相同适用。
  • 能够定义类的实现机制。实现类的热部署,如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的。

理解ClassLoader类的结构

载入class文件

ClassLoader的loadClass採用双亲托付型实现。由于我们实现的ClassLoader都继承于java.lang.ClassLoader类,父载入器都是AppClassLoader。所以在上层逻辑中依然要保证该模型,所以一般不覆盖loadClass函数

protected synchronized Class<?> loadClass ( String name , boolean resolve ) throws ClassNotFoundException{
//检查指定类是否被当前类载入器载入过
Class c = findLoadedClass(name);
if( c == null ){//假设没被载入过。委派给父载入器载入
try{
if( parent != null )
c = parent.loadClass(name,resolve);
else
c = findBootstrapClassOrNull(name);
}catch ( ClassNotFoundException e ){
//假设父载入器无法载入
}
if( c == null ){//父类不能载入,由当前的类载入器载入
c = findClass(name);
}
}
if( resolve ){//假设要求马上链接,那么载入完类直接链接
resolveClass();
}
//将载入过这个类对象直接返回
return c;
}

从上面的代码中。我们能够看到在父载入器不能完毕载入任务时,会调用findClass(name)函数,这个就是我们自己实现的ClassLoader的查找类文件的规则。所以在继承后。我们仅仅须要覆盖findClass()这个函数,实现我们在本载入器中的查找逻辑,并且还不会破坏双亲托付模型

载入资源文件(URL)

我们有时会用Class.getResource():URL来获取对应的资源文件。假设仅仅使用上面的ClassLoader是找不到这个资源的,对应的返回值为null。

下面我们来看Class.getResource()的源代码:

public java.net.URL getResource(String name) {
name = resolveName(name);//解析资源
ClassLoader cl = getClassLoader();//获取到当前类的classLoader
if (cl==null) {//假设为空,那么利用系统类载入器载入
// A system class.
return ClassLoader.getSystemResource(name);
}
//假设获取到classLoader,利用指定的classLoader载入资源
return cl.getResource(name);
}

我们发现Class.getResource()是通过托付给ClassLoader的getResource()实现的,所以我们来看classLoader对于资源文件的获取的详细实现例如以下:

    public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);//这里
}
return url;
}

通过代码我们easy发现。也是双亲委派模型的实现,在不破坏模型的前提下,我们发现我们须要覆写的仅仅是findResource(name)函数

综上

我们在创建自己的ClassLoader时仅仅须要覆写findClass(name)和findResource()就可以

例讲ClassLoader的实现

下面的实现均基于对于ClassLoader抽象类的继承(仅仅给出对于findClass的覆写。由于常理上处理逻辑基本一致)

载入自己定义路径下的class文件

package com.company;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.ByteBuffer; /**
* Created by liulin on 16-4-20.
*/
public class MyClassLoader extends ClassLoader {
private String classpath; public MyClassLoader( String classpath){
this.classpath = classpath;
} @Override
protected Class<? > findClass(String name) throws ClassNotFoundException {
String fileName = getClassFile( name );
byte[] classByte=null;
try {
classByte = getClassBytes(fileName);
}catch( IOException e ){
e.printStackTrace();
}
//利用自身的载入器载入类
Class retClass = defineClass( null,classByte , 0 , classByte.length);
if( retClass != null ) {
System.out.println("由我载入");
return retClass;
}
//System.out.println("非我载入");
//在classPath中找不到类文件,托付给父载入器载入,父类会返回null,由于可载入的话在
//委派的过程中就已经被载入了
return super.findClass(name);
} /***
* 获取指定类文件的字节数组
* @param name
* @return 类文件的字节数组
* @throws IOException
*/
private byte [] getClassBytes ( String name ) throws IOException{
FileInputStream fileInput = new FileInputStream(name);
FileChannel channel = fileInput.getChannel();
ByteArrayOutputStream output = new ByteArrayOutputStream();
WritableByteChannel byteChannel = Channels.newChannel(output);
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
int flag;
while ((flag = channel.read(buffer)) != -1) {
if (flag == 0) break;
//将buffer写入byteChannel
buffer.flip();
byteChannel.write(buffer);
buffer.clear();
}
}catch ( IOException e ){
System.out.println("can't read!");
throw e;
}
fileInput.close();
channel.close();
byteChannel.close();
return output.toByteArray();
} /***
* 获取当前操作系统下的类文件合法路径
* @param name
* @return 合法的路径文件名称
*/
private String getClassFile ( String name ){
//利用StringBuilder将包形式的类名转化为Unix形式的路径
StringBuilder sb = new StringBuilder(classpath);
sb.append("/")
.append ( name.replace('.','/'))
.append(".class");
return sb.toString();
} public static void main ( String [] args ) throws ClassNotFoundException {
MyClassLoader myClassLoader = new MyClassLoader("/home/liulin/byj");
try {
myClassLoader.loadClass("java.io.InputStream");
myClassLoader.loadClass("TestServer");
myClassLoader.loadClass("noClass");
}catch ( ClassNotFoundException e ){
e.printStackTrace();
}
}
}

结果例如以下:



从结果我们看,由于我们载入的类的父载入器是系统载入器,所以调用双亲托付的loadClass,会直接载入掉java.io.InputStream类。仅仅有在载入双亲中没有的TestServer类,才会用到我们自己的findClass载入逻辑载入指定路径下的类文件,满足双亲委派模型详细前面已经讲述过。不再赘述

热部署和加密解密的ClassLoader实现,大同小异。

仅仅是findClass的逻辑发生改变而已

JVM基础(二) 实现自己的ClassLoader的更多相关文章

  1. 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

    目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...

  2. JVM 基础知识

    JVM 基础知识(GC) 2013-12-10 00:16 3190人阅读 评论(1) 收藏 举报 分类: Java(49) 目录(?)[+] 几年前写过一篇关于JVM调优的文章,前段时间拿出来看了看 ...

  3. JVM(二)Java虚拟机组成详解

    导读:详细而深入的总结,是对知识"豁然开朗"之后的"刻骨铭心",想忘记都难. Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我 ...

  4. Java面试题总结之Java基础(二)

    Java面试题总结之Java基础(二) 1.写clone()方法时,通常都有一行代码,是什么? 答:super.clone(),他负责产生正确大小的空间,并逐位复制. 2.GC 是什么? 为什么要有G ...

  5. JVM基础系列第15讲:JDK性能监控命令

    查看虚拟机进程:jps 命令 jps 命令可以列出所有的 Java 进程.如果 jps 不加任何参数,可以列出 Java 程序的进程 ID 以及 Main 函数短名称,如下所示. $ jps 6540 ...

  6. 你的 JVM 基础“大厦”稳健吗?

    [从 1 开始学 JVM 系列] JVM 对于每位 Java 语言编程者来说无疑是"重中之重",尽管我们每天都在与它打交道,却很少来审视它.了解它,慢慢地,它成为了我们" ...

  7. Python全栈开发【基础二】

    Python全栈开发[基础二] 本节内容: Python 运算符(算术运算.比较运算.赋值运算.逻辑运算.成员运算) 基本数据类型(数字.布尔值.字符串.列表.元组.字典) 其他(编码,range,f ...

  8. Bootstrap <基础二十九>面板(Panels)

    Bootstrap 面板(Panels).面板组件用于把 DOM 组件插入到一个盒子中.创建一个基本的面板,只需要向 <div> 元素添加 class .panel 和 class .pa ...

  9. Bootstrap <基础二十八>列表组

    列表组.列表组件用于以列表形式呈现复杂的和自定义的内容.创建一个基本的列表组的步骤如下: 向元素 <ul> 添加 class .list-group. 向 <li> 添加 cl ...

随机推荐

  1. bzoj 1468 Tree(点分治模板)

    1468: Tree Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1527  Solved: 818[Submit][Status][Discuss] ...

  2. Gym - 101981K The 2018 ICPC Asia Nanjing Regional Contest K.Kangaroo Puzzle 暴力或随机

    题面 题意:给你1个20*20的格子图,有的是障碍有的是怪,你可以每次指定上下左右的方向,然后所有怪都会向那个方向走, 如果2个怪撞上了,就融合在一起,让你给不超过5w步,让所有怪都融合 题解:我们可 ...

  3. Visual Studio q启动卡顿

    在开发人员CMD下面执行 Devenv.exe /ResetSettings ,然后顺利打开 总发现vs2015经常把cpu给占满了,导致电脑卡的不要不要的.这是CodeLens引起的,因为装了VAs ...

  4. .net core 下Web API 技术栈

    API文档工具:swagger https://www.cnblogs.com/suxinlcq/p/6757556.html https://www.cnblogs.com/danvic712/p/ ...

  5. centos 修改ssh端口,以支持vsftp

    vi /etc/ssh/sshd_config Port 22 Port 2225执行/etc/init.d/sshd restart   启动SSH服务,这样SSH端口将同时工作与22和2225上. ...

  6. 将DataTable某一列的值整体赋值给 另一个DataTable

    将 DataTable某一列的值,赋值给 另一个DataTable: DataSet _ds=bll.GetAllList(); //将要取其中一列 DataView view = _ds.Table ...

  7. sql学习--update

    两种修改形式 第一种:静态插入 ,notes='began career selling ...balabala' where jc='johnny ca' 第二种: --注意别名和on后边的表连接不 ...

  8. Windows phone开发 网络编程之HttpWebRequest

    HttpWebRequest和WebClient的区别1,HttpWebRequest是个抽象类,所以无法new的,需要调用HttpWebRequest.Create();2,其Method指定了请求 ...

  9. 修改Switch 的颜色

    1:效果图 2:布局 <Switch android:id="@+id/switch_bg" style="@style/switchStyle" and ...

  10. 【SQL】联合语句

    一.UNION操作符 UNION 操作符用于合并两个结果集,在合并的同时去掉重复行,并按合并后结果的第一列升序排列.合并后结果集的列名由第一个结果集的列名确定. UINON连接的两个结果集必须具有相同 ...