当前位置: 首页 > article >正文

Java中如何去自定义一个类加载器

之前写过一篇,关于 类加载器和双亲委派的文章,里边提到过可以根据自己的需要,去写一个自定义的类加载器,正好有人问这个问题,今天有时间就来手写一个自定义的类加载器,并使用这个自定义的类加载器来加载一个class字节码文件。

一、明白为什么需要用到自定义类加载器

官方给的回答是:为 Java 应用程序提供更加灵活和可定制的类加载机制,并实现类的隔离。

为了好理解,我列举一些场景:

1、拓展加载源

其实JVM除了能加载咱们本地编译好的class文件外,还可以加载其他来源的class文件,比如可以

从网络、数据库、从你指定磁盘位置 等地方加载类。

2、实现类隔离

具体来说就是,自定义类加载器可以实现类隔离,避免类之间的冲突和干扰,这个在 tomcat 里就

有大量的应用。

注意:比较两个类是否相等,只有两个类是由同一个类加载器加载的前提下才有意义,否则即使两

个类来自同一个class文件,但是由于加载它们的类加载器不同,那这两个类就不相等。也就是即

使都来自于同一个class文件但是由不同类加载器加载的那就是两个独立的类,用 instanceof 这种

对比都是不同的。

二、大概了解一下ClassLoader类的源码

从上图,可以看到,ClassLoader 它是一个 抽象类,里边的方法也很多,有已经实现的方法,也有空的方法体等着继承它的子类,根据自身场景去具体的实现。

ClassLoader类里虽然方法很多,但是其中咱们最最关注的有 三个方法:

1、loadClass() 方法

loadClass() 方法是用于加载指定名称的类,双亲委派模型核心实现,这个直接由ClassLoader自己实现,一般不建议重写 这个方法。遵循双亲委派模型,首先委派给父类加载器进行加载,如果父类加载器无法加载该类,则自身进行加载

2、findClass() 方法

findClass() 方法是用于查找类的方法,它在ClassLoader里并没有具体的实现,需要由子类加载器去实现,用于查找自身命名空间中的类,自定义类加载器推荐重写这个方法

从上图可以看到 loadClass()方法里的大致逻辑,其中 findClass()方法是在loadClass()方法中被调用的,当loadClass()方法中加载失败后,则调用自己的findClass()方法来完成类加载。但是ClassLoader里对findClass()方法没有实现,需要自己实现具体逻辑,findClass()方法通常是和下边的defineClass()方法一起使用的

可以点 findClass()方法,进去看看,可以看到,ClassLoader里findClass()方法 确实是个空实现,啥也没做,就抛出了个异常。这是个经常使用的到 非常典型的模板设计模式

3、defindClass() 方法

defindClass() 方法是用于定义类的方法,它将字节数组转换为 Class 对象,并将其添加到类加载器的命名空间中。这个方法在ClassLoader里已经实现,findClass()方法通常是和defineClass()方法一起使用的

三、自定义一个ClassLoader类加载器

了解了上边的知识后,下面就开始手写一个类加载器,并尝试着用它来加载咱们准备的一个class字节码文件。

1、自定义一个类,继承ClassLoader类,重写里边的findClass()方法

package com.cj.cl;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoader extends ClassLoader{

    private String path;

    public MyClassLoader(String path) {
        this.path = path;
    }


    @Override
    protected Class<?> findClass(String name){

        String fileName = path + name + ".class";

        System.out.println(fileName);

        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName));
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ) {

            //用IO流去本地磁盘上读取一个class字节码文件
            int len;
            byte[] data = new byte[1024];
            while ((len=bis.read(data))!=-1){
                bos.write(data,0,len);
            }

            //把它转换成字节数组
            byte[] bytes = bos.toByteArray();
            //再把字节数组 传给defindClass() 方法,由defineClass完成类的加载
            Class<?> defineClass = defineClass(null, bytes, 0, bytes.length);
            return defineClass;

        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;

    }

}

自己写的这个类加载器的代码也很简单,就是用IO流去本地磁盘上读取一个class字节码文件,然后把它转换成字节数组,传给defindClass() 方法,defindClass() 方法就能将字节数组转换为 Class 对象,并将其添加到类加载器的命名空间中,完成类的加载。

2、准备一个编译好的class字节码文件

我在idea里随便写一个Student类

然后 在这个类上 点击右键

就可以直接进入该类所在的目录里了,进来之后,在下边的命令行 输入 javac Student.java

编译成功后,当前目录会多出一个编译好的class字节码文件

然后把这个class文件 剪切到 E盘

3、测试自己写的自定义类加载器

写个main方法来测试

package com.cj;

import com.cj.cl.MyClassLoader;

public class Main {

    public static void main(String[] args) throws Exception {

        MyClassLoader myClassLoader = new MyClassLoader("E:/");

        //使用自己自定义的类加载器,去加载Student.class 字节码文件
        Class<?> clazz = myClassLoader.loadClass("Student");

        //得到class字节码后,使用反射来创建对象
        Object obj = clazz.getDeclaredConstructor().newInstance();

        //打印出obj这个对象所属的类名
        System.out.println(obj.getClass().getName());
        //打印出obj这个对象所属的类 用的类加载器的名称
        System.out.println(obj.getClass().getClassLoader().getClass().getName()+"类加载器");
    }

}

可以看到,自己写的类加载器,已经把我电脑E盘下的 Student.class字节码文件,加载进JVM了,并且使用自定义类加载器加载进来的字节码反射生成了一个obj对象。

下面,可以再验证一下上边说的那个:即使都来自于同一个class文件但是由不同类加载器加载的那就是两个独立的类,用 instanceof 这种对比都是不同的。

从下图可以看到,当前项目里的Student的字节码是由jdk.internal.loader.ClassLoaders$AppClassLoader 这个JVM内部自带的应用程序类加载器加载进来的,并且用instanceof 对比了一下两个Student,结果打印出了 false

ok,今天就写到这里吧。希望对粉丝们理解java的自定义加载器有所帮助。

纯手敲 原创不易,如果觉得对你有帮助,可以关注一下,打赏一下,感谢。


http://www.kler.cn/a/583936.html

相关文章:

  • SQL Server查询优化
  • Axure设计之下拉多选框制作教程C(中继器)
  • 【MySQL 中 `TINYINT` 类型与布尔值的关系】
  • 宇树人形机器人开源模型
  • 【一键让照片动起来】阿里万相2.1图生视频+蓝耘智算零门槛部署指南
  • 正则应用--java算法
  • 【Java项目】基于JSP的咨询交流论坛系统
  • 用 Python 检测两个文本文件的相似性的几种方法
  • 人工智能混合编程实践:Python ONNX进行图像超分重建
  • 【探秘机器人:从当下到未来的科技跃迁】
  • VSCode集成C语言开发环境
  • 八叉树地图的原理与实现
  • 基于GoogleNet深度学习网络和GEI步态能量提取的步态识别算法matlab仿真,数据库采用CASIA库
  • 归并排序的一些介绍
  • 【Linux】线程
  • 贪心算法——c#
  • FX-std::map
  • CLR中的类型转换
  • Redis7——进阶篇(六)
  • Chat-TTS-UI:文字转语音 - 本地部署方案