JVM基础(二) 实现自己的ClassLoader

为何要花时间实现自己的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;
    }

通过代码我们容易发现,也是双亲委派模型的实现,在不破坏模型的前提下,我们发现我们需要覆写的只是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的逻辑发生改变而已

时间: 2024-12-06 09:23:21

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

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

目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.ClassLoader源码分析 11.自定义类加载器 12.加载类的三种方式 13.总结 14.特别注意 @ 前言 你是否真的理解java的类加载机制?点进文章的盆友不如先来做一道非常常见的面试题,如果你能做出来,可能你早已掌握并理解了java的类加载机制,若结果出乎你的意料,那就很有必要来了解了解j

jvm基础理解

一.jvm运行时内存区域 包含堆,虚拟机栈,本地栈(调用native方法时用到),方法区(perm区),程序计数器. 假设32位操作系统,这时系统限制每个进程大小为2G.这样上述这些区域(对于本地栈及程序计数器来讲,是无法设置的,hotspot有提供-Xoss参数用于设置本地方法栈.但实际是无效的)可以用相应参数设置,共同划分全部2G内存. 注意还有一块直接内存,不属于JAVA运行时区域.但是它的空间用得太多的话,还是受限于物理内存和虚拟内存总大小,因此也会抛出OutOfMemory.典型的re

关于Java面试知识点解析——JVM基础篇

跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽.切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来(期间也没有准备充分),到底是因为技术原因(影响自己的发展,偏移自己规划的轨迹),还是钱给少了,不受重视. 准备不充分的面试,完全是浪费时间,更是对自己的不负责(如果title很高,当我没说).今天给大家分享下 Java面试知识点解析--JVM基础篇 1)Java 是如何实现跨平台的? 注意:跨平台的是 Java 程序,而不是 JVM.JVM 是用 C/C++ 开发的,是

Html5开发——html+css基础二(个人博客一)

今天没有写完,而且写的还有点问题,所以今天就先不上传代码了(ps:快写完了才发现布局有问题,导致代码太多,感觉写的不是很好,所以今天先分析一下布局) 第一步先写一个大的div用来放ABC三个部分,这个大的div居中! 第二步分别写ABC三个部分,ABC三个部分分别使用浮动(float)来定位.A和B都各使用了一张非常小的图片,通过重复(repeat)属性生成A和B 第三步C部分分别写好3~16这几个模块,在通过组合利用浮动定位.分组如下: E:3 F:4.7.10.13 G:5.8.11.14

Scala 中的函数式编程基础(二)

主要来自 Scala 语言发明人 Martin Odersky 教授的 Coursera 课程 <Functional Programming Principles in Scala>. 2. Higher Order Functions 把其他函数作为参数或者作为返回值,就是 higher order functions,python 里面也可以看到这样使用的情形.在酷壳上的博客有一个例子就是将函数作为返回值. 2.1 匿名函数 在 python 里边叫 lambda 函数,常常与 map(

Python全栈开发【基础二】

Python全栈开发[基础二] 本节内容: Python 运算符(算术运算.比较运算.赋值运算.逻辑运算.成员运算) 基本数据类型(数字.布尔值.字符串.列表.元组.字典) 编码与进制转换 Python 运算符 1.算术运算: 2.比较运算: 3.赋值运算: 4.逻辑运算:  5.成员运算: 基本数据类型 1.数字 int(整型) 1 class int(object): 2 """ 3 int(x=0) -> integer 4 int(x, base=10) -&g

R语言基础(二) 可视化基础

> which.max(apply(x[c("x1","x2","x3")], 1, sum))49 > x$num[which.max(apply(x[c("x1","x2","x3")], 1, sum))][1] 2005138149 > hist(x$x1) > plot(x$x1,x$x2) > table(x$x1) 80 81 82 83 84

Bootstrap &lt;基础二十五&gt;警告(Alerts)

警告(Alerts)以及 Bootstrap 所提供的用于警告的 class.警告(Alerts)向用户提供了一种定义消息样式的方式.它们为典型的用户操作提供了上下文信息反馈. 您可以为警告框添加一个可选的关闭按钮.为了创建一个内联的可取消的警告框,请使用 警告(Alerts) jQuery 插件. 您可以通过创建一个 <div>,并向其添加一个 .alert class 和四个上下文 class(即 .alert-success..alert-info..alert-warning..ale

图像处理之基础---二维卷积运算原理剖析

卷积运算(Convolution)是通过两个函数f 和g 生成第三个函数的一种数学算子,表示函数f 与经过翻转和平移与g 的重叠部分的累积.如果将参加卷积的一个函数看作区间的指示函数,卷积还可以被看作是“滑动平均”的推广.假设: f(x),g(x)是R1上的两个可积函数,并且积分是存在的.这样,随着 x 的不同取值,这个积分就定义了一个新函数h(x),称为函数f 与g 的卷积,记为h(x)=(f*g)(x). 两个向量卷积,说白了就是多项式乘法.下面用个矩阵例子说明其工作原理: a和d的卷积就是