JVM基础学习——自定义类加载器

15 篇文章 0 订阅
2 篇文章 0 订阅

一、类加载器介绍

在这里插入图片描述

1)引导类加载器(BootstrapClassLoader)

也叫启动类或根类加载器,它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

2)扩展类加载器(ExtensionsClassLoader)

它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。

3)系统类加载器(SystemClassLoader)

被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。

二、自定义加载器

所有的加载器除了引导类加载器,其他都是直接或间接的继承了ClassLoader,调用的方法是使用它的loadClass方法进行调用,加载要加载的class文件产生Class对象使用。这里自定义的加载器要重新findClass方法并通过defineClass方法返回创建的Class对象。

1、创建思路

1)获取class的路径

2)读取class的字节码

3)调用defineClass方法

/**
 * 参数一是类的全限定名称
 * 参数二是字节码
 * 参数三是读取字节码的开始偏移量
 * 参数四是读取字节码的结束偏移量
 * 这里表示读取全部字节码
 */
Class<?> defineClass(name, classData, 0, classData.length);

4)调用class 文件

2、创建自定义加载器

package com.main.jvmtexst.classloadertest.test2;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
 * 自定义加载器
 */
public class CustomizeClassLoader extends ClassLoader {
    //目录地址
    private String path;
    //类全限定名称地址
    private String classPath;

    //默认使用系统加载器加载
    public CustomizeClassLoader(String path) {
        this.path = path;
    }

    //选择自己的父类加载器,如果使用引导类加载器则使用null代替
    public CustomizeClassLoader(ClassLoader parent, String path) {
        super(parent);
        this.path = path;
    }

    private String getClassPath() {
        //处理全限定名,转换成地址形式
        List<String> nameList = Arrays.asList(classPath.split("\\."));
        StringBuilder nameBuilder = new StringBuilder();
        nameList.forEach(item -> nameBuilder.append(item).append(File.separator));
        classPath = nameBuilder.toString();
        classPath = classPath.substring(0, classPath.length() - 1);

        //返回全地址
        return path + File.separator + classPath + ".class";
    }

    /**
     * 重写findClass方法,通过landClass访问
     *
     * @param name 类的全限定名称
     * @return 返回Class对象
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        this.classPath = name;
        byte[] classData = null;

        //读取字节码文件流
        try (FileInputStream fileInputStream = new FileInputStream(this.getClassPath());
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();) {

            int ch = 0;
            while ((ch = fileInputStream.read()) != -1) {
                byteArrayOutputStream.write(ch);
            }
            classData = byteArrayOutputStream.toByteArray();

        } catch (IOException e) {
            throw new ClassNotFoundException(name);
        }

        /**
         * 参数一是类的全限定名称
         * 参数二是字节码
         * 参数三是读取字节码的开始偏移量
         * 参数四是读取字节码的结束偏移量
         * 这里表示读取全部字节码
         */
        return defineClass(name, classData, 0, classData.length);
    }
}

3、创建class测试文件

package com;

public class Test {
    public static int v1 = 1;

    public Test() {
        System.out.println(Test.class.getName() + "调用成功:" + this.hashCode());
        System.out.println(Test.class.getName() + "加载器为:" + this.getClass().getClassLoader());
    }
}

在根目录运行命令行

javac -d .\class -encoding UTF-8  com\Test.java

此时根目录下的有文件com/Test.class

4、创建测试类

package com.main.jvmtexst.classloadertest.test2;

import java.lang.reflect.Field;

public class CustomizeClassLoaderTest {
    public static void main(String[] args) throws Exception {
        //设置读取目录
        String path = "D:\\selfProjects\\Test\\JAVABASIC\\class\\";
        //创建自定义加载器
        //使用默认加载器
        ClassLoader classLoader1 = new CustomizeClassLoader(path);
        //使用引导类加载器
        ClassLoader classLoader2 = new CustomizeClassLoader(null, path);
        //自定义加载器
        ClassLoader classLoader3 = new CustomizeClassLoader(classLoader1, path);

        System.out.println("classLoader1的加载器 : " + classLoader1.getParent());
        System.out.println("classLoader2的加载器 : " + classLoader2.getParent());
        System.out.println("classLoader3的加载器 : " + classLoader3.getParent());

        //测试加载指定目录下的类文件
        //加载并获取指定类的Class对象
        Class loader = classLoader1.loadClass("com.Test");
        //通过反射创建实例对象
        Object test1 = loader.newInstance();
        //获取实例对象的属性
        Field v1 = loader.getField("v1");

        System.out.println("test1的hashCode : " + test1.hashCode());
        System.out.println("获取静态对象v1 : " + v1 + " = " + v1.getInt(test1));
    }
}

测试结果:

classLoader1的加载器 : sun.misc.Launcher$AppClassLoader@18b4aac2
classLoader2的加载器 : null
classLoader3的加载器 : com.main.jvmtexst.classloadertest.test2.CustomizeClassLoader@2ef3eef9
com.Test调用成功:1201484275
com.Test加载器为:com.main.jvmtexst.classloadertest.test2.CustomizeClassLoader@2ef3eef9
test1的hashCode : 1201484275
获取静态对象v1 : public static int com.Test.v1 = 1

这样我们就完成了自定义加载类的创建和使用了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值