如何实现自定义类加载器_开发者不可以自定义类加载器

如何实现自定义类加载器_开发者不可以自定义类加载器为什么要有类加载器类加载的过程初识类加载器类加载机制自定义类加载器为什么要有类加载器我们知道java中所有的二进制文件,最后都是要放在jvm中解释运行的。纯粹的二进制文件,其实并没有什么卵用。jvm在第一次使用或者预加载时,都要将某个类的二进制文件加载进去,这时候不可避免的需要用到一个加载的触手,就是这个类加载器啦。类的加载过程简单来说,一般可分为加载、连接、初始化三个过程。加载,顾名思义

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

  • 为什么要有类加载器
  • 类加载的过程
  • 初识类加载器
  • 类加载机制
  • 自定义类加载器

为什么要有类加载器

我们知道java中所有的二进制文件,最后都是要放在jvm中解释运行的。纯粹的二进制文件,其实并没有什么卵用。jvm在第一次使用或者预加载时,都要将某个类的二进制文件加载进去,这时候不可避免的需要用到一个加载的触手,就是这个类加载器啦。

类的加载过程

简单来说,一般可分为加载、连接、初始化三个过程。

类的加载过程

加载,顾名思义,就是将类的class文件读入到内存中,创建一个Class对象,你可能已经知道了,java中所有的类都是Class类的实例。jvm已经中提供了一些系统类加载器。

连接,我的理解就是将这些二进制数据放到jre中,准备初始化。而这一步一般也可分作三个阶段:

  • 验证:保证类没有被加载跑偏,而且结构没被破坏;

  • 准备:为各种变量分配内存空间,并设置它们的初始值;

  • 解析:把类中的符号引用转换为直接引用;

初始化,主要就是类变量进行初始化,这里一般指static声明的变量,或者静态代码块。这里需要提一下,熟悉构造器的童鞋应该都有这样的体验:java中的类其实都很“孝顺”的——调用自己构造器之前,必先调用父类的构造器。初始化也是如此,如果该类的直接父类没有被初始化,则需要先初始化它的直接父类,如此调用直到Object类。另外由static final修饰的常量,一般在编译时值已经确定,通过类来访问它时则不会再对其进行初始化,有点类似于直接量。

初识类加载器

前面已经说过,类加载器负责将.classs文件加载到内存中,同时生成一个Class对象,当下一个次需要加载时,JVM中如果存在同一个类,那么加载就不会继续。有童鞋可能已经有问题了:怎么判断是不是需要再加载呢?也就是说啥样的两个类才算同一个类?

对了,名字一样。在Java中,一个类通常会用它的命名空间(包名+类名)来作为它的唯一标识。Jvm中进一步约束了条件,通常会把命名空间和其类加载器作为它的唯一标识,也就是说同一个类必须满足包名、类名、加载器都一样才行。举个例子来讲,我们现在有一个fruit包,里面有一个Apple类,被类加载器classloader1加载了,那么这个类的实例在Jvm中就可以标识为(Apple,fruit,classloader1),很明显Jvm认为它与(Apple,fruit,classloader2)并不是同一个类(即所谓的ClassLoader隔离)。根加载器

一般的,Jvm中的加载器如果按照继承来分,可分为ClassLoader子类和非ClassLoader子类,如果按照层次结构来分,则可分为这三种:

  • 根类加载器(Bootstrap ClassLoader),主要加载Java的核心类库,它就不是ClassLoader的子类,由更屌的c++实现的;

  • 扩展类加载器(Extension ClassLoader),主要负责加载jre扩展目录中jar包的类,就是另外添加的放在扩展路径下的jar文件;

  • 系统类加载器(System ClassLoader),这个就更常见了,你写的java文件或者classpath变量所指向的jar包和类,都是靠它来加载的。

类加载器关系图

类加载机制

  • 全盘负责
  • 父类委托
  • 缓存机制

类加载机制

自定义类加载器

由上面图很容易发现除了根类加载器之外,所有的类加载器都是ClassLoader的子类。那我们现在要自定义自己的类加载器,很自然地就要继承自ClassLoader。源码中提供了大量需要我们重写的方法,其中有两个是比较关键的:

loadClass():是ClassLoader的入口,加载器可根据类名来加载指定类对应的Class对象;

findClass():根据指定名来查找类;

两个方法关系紧密,如下图所示:

两个方法的关系

这下明白通常推荐重写findClass()而不是loadClass()的原因了吧,重写findClass()不仅简单,而且还能都避免覆盖默认加载器的父类委托、缓存机制等,一举果有两虎之功呃。

再来一张它们的类关系图吧

主要类关系

有了上面的基础,下面我们动手写自己的类加载器:

public class MyClassLoader extends ClassLoader {

    /** * @param args * @throws ClassNotFoundException * @throws SecurityException * @throws NoSuchMethodException * @throws InvocationTargetException * @throws IllegalArgumentException * @throws IllegalAccessException */
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        // TODO Auto-generated method stub
        if (args.length == 0) {
            System.out.println("没有类啊");
        }
        // 取出第一个参数,就是需要运行的类
        String procressClass = args[0];
        // 剩余参数为运行目标类的参数,将这些参数复制到一个新数组中
        String[] procress = new String[args.length - 1];
        System.arraycopy(args, 1, procress, 0, procress.length);
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> class1 = myClassLoader.loadClass(procressClass);
        Method main = class1.getMethod("main", (new 
        String[0]).getClass());
        Object argsArray[] = { procress };
        main.invoke(null, argsArray);
    }

    /** * @TODO 读取文件内容 */
    public byte[] getBytes(String fileName) {
        File file = new File(fileName);
        long len = file.length();
        byte[] raw = new byte[(int) len];
        try {
            FileInputStream fileInputStream =
             new FileInputStream(file);
            try {
                int r = fileInputStream.read(raw);
                fileInputStream.close();
                if (r != len)
                    throw new IOException("fail to read the file...");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return raw;
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    /** * @TODO 编译java文件 */
    public boolean complie(String javaFile) {
        System.out.println("正在编译...");
        Process process = null;
        try {
            process = Runtime.getRuntime().exec("javac " + javaFile);
            try {
                process.waitFor();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        int result = process.exitValue();
        return result == 0;
    }

    /** * @TODO 关键,重写findClass方法 */
    @Override
    protected Class<?> findClass(String arg0) throws ClassNotFoundException {
        // TODO Auto-generated method stub
        Class<?> class1 = null;
        String filePath = arg0.replaceAll(".", "/");
        String className = filePath + ".class";
        String javaName = filePath + ".java";
        File javaFile = new File(javaName);
        File classFile = new File(className);
        if (javaFile.exists()
                && (!classFile.exists() || javaFile.lastModified() > classFile .lastModified())) {
            if (!complie(javaName) || !classFile.exists()) {
                throw new ClassNotFoundException(javaName + " Class找不到");
            }
        }
        if (classFile.exists()) {
            byte[] raw = getBytes(className);
            class1 = defineClass(arg0, raw, 0, raw.length);
        }

        if (class1 == null) {
            throw new ClassNotFoundException(javaName + " 加载失败");
        }

        return class1;
    }

    }

下面再写一个测试类:

public class ClassLoaderTest {
public static void main(String[] args) {
    // TODO Auto-generated method stub
    for (String arg : args) {
        System.out.println("运行时的参数: " + arg);
    }
    }
}

然后无需编译,在命令行下运行:

java MyClassLoader ClassLoaderTest 自定义类加载器

是不是就看到输出啦^_^

值得说明的是,ClassLoader里面还提供了许多功能强大的方法,如final defineClass(),findSystemClass()等,这些都由jvm封装好了,职能明确,无需重写,有兴趣的低头看源码吧

代码附上了部分注释,有疑问的童鞋欢迎吐槽拍砖。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/196960.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 用HashSet存储自定义对象

    用HashSet存储自定义对象

  • 数据结构之二叉树的前序遍历、中序遍历、后序遍历、层序遍历「建议收藏」

    数据结构之二叉树的前序遍历、中序遍历、后序遍历、层序遍历「建议收藏」最近也是在准备笔试,由于没有系统的学过数据结构,所以每次在考到二叉树的遍历的时候都是直接跪,次数多了也就怒了,前些天也是准备论文没时间整这些,现在提交了,算是稍微轻松点了,所以花了半天的时间来学了下二叉树。现在记下来,以便后序查阅。一、二叉树的遍历概念  1. 二叉树的遍历是指从根结点触发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。(1).前(

  • Mybatis分页查询limit

    Mybatis分页查询limit首先,写一下分页查询的原理:sql语句:#语法SELECT*FROMtableLIMITstratIndex,pageSizeSELECT*FROMtableLIMIT5,10;//检索记录行6-15#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为-1:SELECT*FROMtableLIMIT95,-1;//检索记录行96-last.#如果只给定一个参数,它表示返回最大的记录行数目:SELECT*FROMtableL

  • oracle中的sequence

    oracle中的sequence1、什么是sequence?在oracle中sequence就是序号,每次取的时候它会自动增加。sequence与表没有关系。2、sequence的作用?当需要建立一个自增字段时,需要用到sequen

  • countdowntimer_TIMESTAMPDIFF

    countdowntimer_TIMESTAMPDIFF需求:加载某一个界面,在页面中待5秒后再关闭效果图如下:设置了一个点击事件,当文字显示为Skipactivity时,点击跳转界面。代码及介绍如下图:核心功能代码如下Android自带的CountDownTimer这个工具类,也是通过Handler和子线程来实现的。//倒计时工具类CountDownTimer//CountDownTimer的构造方法有两个参数…

  • Android 浏览器内核浅谈[通俗易懂]

    Android 浏览器内核浅谈[通俗易懂]目前,移动设备浏览器上常用的内核有Webkit,Blink,Trident,Gecko等,其中iPhone和iPad等苹果iOS平台主要是WebKit,Android 4.4之前的android系统浏览器内核是WebKit,Android4.4系统浏览器切换到了Chromium(内核是Webkit的分支Blink),WindowsPhone8系统浏览器内核是Trident。 1.W

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号