一个自定义类加载器ClassLoader示例

我们的自定义类加载器

package cn.gd.cjz.class_loader;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 自定义类加载器
 */
public class CustomClassLoader extends ClassLoader {
    /**类名**/
    private String name;
    /**通过构造方法设置父类加载器和要热加载的类名**/
    public CustomClassLoader(ClassLoader parent , String name) {
        super(parent);
        if(name == null || name.length() <= 0)
            throw new NullPointerException();

        this.name = name;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;
        /**如果是我们想要热加载的类则调用我们重写的findClass方法来加载**/
        if(this.name.equals(name) && !"java".equals(name)){
            /**先看看要热加载的类之前是否已经加载过了,因为一个类加载器只能加载一个类一次,加载多次会报异常**/
            clazz = findLoadedClass(name);
            /**clazz==null说明之前没有加载过**/
            if(clazz == null)
                clazz = findClass(name);

            /**
             * 类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。其中验证、准备、解析统称为连接
             * 如果要连接类
             */
            if(resolve)
                resolveClass(clazz);//如果类已连接过,resolveClass方法会直接返回
            return clazz;
        }
        return super.loadClass(name , resolve);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = c2f(name);
        byte[] bytes = f2b(fileName);
        return defineClass(name, bytes, 0, bytes.length);
    }

    /**
     * 类名转为文件名
     * @param name
     * @return
     */
    private String c2f(String name){
        /**编译后的class文件存放的目录**/
        String baseDir = "F:\\idea_workspace\\Test\\target\\classes\\";
        name = name.replace("." , File.separator);
        name = baseDir + name + ".class";
        return name;
    }

    /**
     * 读取文件byte数组
     * @param fileName
     * @return
     */
    private byte[] f2b(String fileName){
        RandomAccessFile file = null;
        FileChannel channel = null;
        byte[] bytes = null;
        try {
            /**随机存取文件对象,只读取模式**/
            file = new RandomAccessFile(fileName , "r");
            /**NIO文件通道**/
            channel = file.getChannel();
            /**NIO字节缓冲**/
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int size = (int) channel.size();
            bytes = new byte[size];
            int index = 0;
            /**从NIO文件通道读取数据**/
            while (channel.read(buffer) > 0){
                /**字节缓冲从写模式转为读取模式**/
                buffer.flip();
                while (buffer.hasRemaining()){
                    bytes[index] = buffer.get();
                    ++index;
                }
                /**字节缓冲的readerIndex、writerIndex置零**/
                buffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (channel != null) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bytes;
    }

    /**
     * 热加载类
     * @return
     */
    public Class<?> loadClass(){
        try {
            return loadClass(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

将要被热加载的类接口

package cn.gd.cjz.class_loader;

/**
 * 测试类接口
 */
public interface IPrinter {
    public void print();
}

将要被热加载的类

package cn.gd.cjz.class_loader;

/**
 * 测试类
 */
public class Printer implements IPrinter {
    @Override
    public void print() {
        System.out.println("彪悍的人生不需要解释是谁说的?");
    }
}

好了,写个测试类来测试一下

package cn.gd.cjz.class_loader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/***
 * 自定义类加载器测试类
 */
public class CustomClassTest {
    public static void main(String[] args) {
        /**要进行热加载的类名**/
        String name = "cn.gd.cjz.class_loader.Printer";
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        IPrinter printer = null;
        while (true) {
            System.out.println("输入任意字符进行热加载,直接敲回车键退出程序");
            try {
                String line = reader.readLine();
                if(line != null && line.length() > 0){
                    CustomClassLoader loader = new CustomClassLoader(Thread.currentThread().getContextClassLoader() , name);
                    Class<?> clazz = loader.loadClass();
                    /**
                     * 被子加载器加载的类拥有被父加载器加载的类的可见性
                     * Printer是由自定义类加载器加载的,
                     * 而它的父类IPrinter是由系统类加载器加载的,
                     * 因此IPrinter对于Printer具有可见性,
                     * 因此转型成功,并不会因为类加载器不同导致ClassCastException异常
                     */
                    printer = (IPrinter) clazz.newInstance();
                    /**看看是否热加载成功了**/
                    printer.print();
                }else{
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

首先运行测试类,输入任意字符看看控制台输出了什么?再把Printer类改成下面的代码,编译后再在控制台输入任意字符,看看控制台又输出了什么?

package cn.gd.cjz.class_loader;

/**
 * 测试类
 */
public class Printer implements IPrinter {
    @Override
    public void print() {
        System.out.println("锤子老罗说的。");
    }
}


以下是我的测试结果:



  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值