如何自己实现一个热加载?如何定义自己的类加载器?

如何自己实现一个热加载?

热加载:在不停止程序运行的情况下,对类(对象)的动态替换

热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境。
在默认情况下,类加载器是遵循双亲委派规则的。所以我们要实现热加载,那么我们需要加载的那些类就不能交给系统加载器来完成。所以我们要自定义类加载器来写我们自己的规则。

我们怎么才能手动写一个类的热加载呢?
Java 程序在运行的时候,首先会把 class 类文件加载到 JVM 中,而类的加载过程又有五个阶段,五个阶段中只有加载阶段用户可以进行自定义处理,所以我们如果能在程序代码更改且重新编译后,让运行的进程可以实时获取到新编译后的 class 文件,然后重新进行加载的话,那么理论上就可以实现一个简单的 Java 热加载

所以我们可以得出实现思路:

  1. 实现自己的类加载器。
  2. 从自己的类加载器中加载要热加载的类。
  3. 不断轮训要热加载的类 class 文件是否有更新。
  4. 如果有更新,重新加载。

如何自定义自己的类加载器

要想实现自己的类加载器,只需要继承ClassLoader类即可。而我们要打破双亲委派规则,那么我们就必须要重写loadClass方法,因为默认情况下loadClass方法是遵循双亲委派的规则的。

在 Java 中,类加载器也就是 ClassLoader. 所以如果我们想要自己实现一个类加载器,就需要继承 ClassLoader 然后重写里面 findClass的方法,同时因为类加载器是 双亲委派模型实现(也就说。除了一个最顶层的类加载器之外,每个类加载器都要有父加载器,而加载时,会先询问父加载器能否加载,如果父加载器不能加载,则会自己尝试加载)所以我们还需要指定父加载器。

1.新建自定义类加载器

MyClassLoader.java

/**
 * <p>
 * 自定义 Java类加载器来实现Java 类的热加载
 *
 */
public class MyClassLoader extends ClassLoader {

    /** 要加载的 Java 类的 classpath 路径 */
    private String classpath;

    public MyClassLoader(String classpath) {
        // 指定父加载器
        super(ClassLoader.getSystemClassLoader());
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = this.loadClassData(name);
        return this.defineClass(name, data, 0, data.length);
    }

    /**
     * 加载 class 文件中的内容
     *
     * @param name
     * @return
     */
    private byte[] loadClassData(String name) {
        try {
            // 传进来是带包名的
            name = name.replace(".", "//");
            FileInputStream inputStream = new FileInputStream(new File(classpath + name + ".class"));
            // 定义字节数组输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = inputStream.read()) != -1) {
                baos.write(b);
            }
            inputStream.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

2.定义要热加载的类

我们假设某个接口(BaseManager.java)下的某个方法(logic)要进行热加载处理。

首先定义接口信息。

public interface BaseManager {
    public void logic();
}

接口实现类:MyManager.java

public class MyManager implements BaseManager {
    @Override
    public void logic() {
        System.out.println(LocalTime.now() + ": Java类的热加载Oh~~~~");
    }
}

后面我们要做的就是让这个类可以通过我们的 MyClassLoader进行自定义加载。类的热加载应当只有在类的信息被更改然后重新编译之后进行重新加载。所以为了不意义的重复加载,我们需要判断 class是否进行了更新,所以我们需要记录 class类的修改时间,以及对应的类信息。

所以定义一个类LoadInfo用来记录某个类对应的某个类加载器以及上次加载的 class 的修改时间。

/**
 * <p>
 * 封装加载类的信息
 */
public class LoadInfo {

    /** 自定义的类加载器 */
    private MyClassLoader myClassLoader;

    /** 记录要加载的类的时间戳-->加载的时间 */
    private long loadTime;

    /** 需要被热加载的类 */
    private BaseManager manager;

    public LoadInfo(MyClassLoader myClassLoader, long loadTime) {
        this.myClassLoader = myClassLoader;
        this.loadTime = loadTime;
    }

    public MyClassLoader getMyClassLoader() {
        return myClassLoader;
    }

    public void setMyClassLoader(MyClassLoader myClassLoader) {
        this.myClassLoader = myClassLoader;
    }

    public long getLoadTime() {
        return loadTime;
    }

    public void setLoadTime(long loadTime) {
        this.loadTime = loadTime;
    }

    public BaseManager getManager() {
        return manager;
    }

    public void setManager(BaseManager manager) {
        this.manager = manager;
    }
}

3. 热加载获取类的信息

在实现思路里,我们知道轮训检查 class 文件是不是被更新过,所以每次调用要热加载的类时,我们都要进行检查类是否被更新然后决定要不要重新加载。为了方便这步的获取操作,可以使用一个简单的工厂模式进行封装。

要注意是加载 class 文件需要指定完整的路径,所以类中定义了 CLASS_PATH 常量。
注:此处的CLASS_PATHMY_MANAGER常量路径改成自己的代码路径
ManagerFactory.java

/**
 * <p>
 * 加载 manager 的工厂
 */
public class ManagerFactory {

    /** 记录热加载类的加载信息 */
    private static final Map<String, LoadInfo> loadTimeMap = new HashMap<>();

    /** 要加载的类的 classpath */
    public static final String CLASS_PATH = "D:\\appsDownload\\代码\\mine\\leetcode\\";

    /** 实现热加载的类的全名称(包名+类名 ) */
    public static final String MY_MANAGER = "com.fwind.热加载.MyManager";

    public static BaseManager getManager(String className) {
        File loadFile = new File(CLASS_PATH + className.replaceAll("\\.", "/") + ".class");
        // 获取最后一次修改时间
        long lastModified = loadFile.lastModified();
        System.out.println("当前的类时间:" + lastModified);
        // loadTimeMap 不包含 ClassName 为 key 的信息,证明这个类没有被加载,要加载到 JVM
        if (loadTimeMap.get(className) == null) {
            load(className, lastModified);
        } // 加载类的时间戳变化了,我们同样要重新加载这个类到 JVM。
        else if (loadTimeMap.get(className).getLoadTime() != lastModified) {
            load(className, lastModified);
        }
        return loadTimeMap.get(className).getManager();
    }

    /**
     * 加载 class ,缓存到 loadTimeMap
     *
     * @param className
     * @param lastModified
     */
    private static void load(String className, long lastModified) {
        MyClassLoader myClassLoader = new MyClassLoader(className);
        Class loadClass = null;
        // 加载
        try {
            loadClass = myClassLoader.loadClass(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        BaseManager manager = newInstance(loadClass);
        LoadInfo loadInfo = new LoadInfo(myClassLoader, lastModified);
        loadInfo.setManager(manager);
        loadTimeMap.put(className, loadInfo);
    }

    /**
     * 以反射的方式创建 BaseManager 的子类对象
     *
     * @param loadClass
     * @return
     */
    private static BaseManager newInstance(Class loadClass) {
        try {
            return (BaseManager)loadClass.getConstructor(new Class[] {}).newInstance(new Object[] {});
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }
}

4.热加载测试

直接写一个线程不断的检测要热加载的类是不是已经更改需要重新加载,然后运行测试即可。
MsgHandle.java

/**
 * <p> * * 后台启动一条线程,不断检测是否要刷新重新加载,实现了热加载的类
 */
public class MsgHandle implements Runnable {
    @Override
    public void run() {
        while (true) {
            BaseManager manager = ManagerFactory.getManager(ManagerFactory.MY_MANAGER);
            manager.logic();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

主线程: ClassLoadTest.java

public class ClassLoadTest {
    public static void main(String[] args) {
        new Thread(new MsgHandle()).start();
    }
}

代码已经全部准备好了,最后一步,可以启动测试了。如果你是用的是 Eclipse ,直接启动就行了;如果是 IDEA ,那么你需要 DEBUG 模式启动(IDEA 对热加载有一定的限制)。

启动后看到控制台不断的输出:
在这里插入图片描述

这时候我们随便更改下 MyManager 类的 logic 方法的输出内容然后保存。

public class MyManager implements BaseManager {
    @Override
    public void logic() {
        System.out.println(LocalTime.now() + ": Java类的热加载Oh~~~~");
        System.out.println(LocalTime.now() + ": Java类的热加载Oh~~~~");
    }
}

可以看到控制台的输出已经自动更改了,IDEA 在更改后需要按 CTRL + F9 (重新加载类不重启程序)。
在这里插入图片描述

实现热加载参考:
https://blog.csdn.net/weixin_38405253/article/details/103535360
https://blog.csdn.net/weixin_43133353/article/details/95995074

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
假设我们有一个数据集,其中包含了一些关于花朵的特征(如花瓣长度、花瓣宽度、花萼长度、花萼宽度等),并且每个数据点被标记为三种不同类型的花(setosa、versicolor、virginica)之一。我们的目标是构建一个模型,根据这些特征来预测花属于哪一种类型。 这是一个典型的分类问题,可以使用神经网络来解决。我们可以使用一个多层感知(MLP)来构建我们的神经网络,其中每个输入特征都被映射到一个隐藏层中的一组节点,然后通过一系列线性和非线性变换来计算输出层中的一组节点,最终输出我们的分类预测。 下面是一个简单的 MATLAB 代码示例,该示例使用一个具有一个隐藏层的MLP来对鸢尾花数据集进行分类: ```matlab % 鸢尾花数据集 load fisheriris; % 将标签转换为独编码形式 labels = dummyvar(grp2idx(species)); % 将数据集分成训练集和测试集 [trainInd,testInd] = dividerand(size(meas,1),0.7,0.3); trainInputs = meas(trainInd,:)'; trainTargets = labels(trainInd,:)'; testInputs = meas(testInd,:)'; testTargets = labels(testInd,:)'; % 定义神经网络结构 hiddenLayerSize = 10; net = patternnet(hiddenLayerSize); % 设置训练参数 net.trainParam.epochs = 100; net.trainParam.lr = 0.01; % 训练神经网络 [net,tr] = train(net,trainInputs,trainTargets); % 用测试集进行预测,并计算分类准确率 testOutputs = net(testInputs); testClasses = vec2ind(testOutputs)'; testAccuracy = sum(testClasses == vec2ind(testTargets)) / numel(testClasses); disp(['Test accuracy: ', num2str(testAccuracy)]); ``` 在这个示例中,我们首先了鸢尾花数据集,并将标签转换为独编码形式。然后,我们将数据集分成训练集和测试集,并将输入和目标变量转换为适合 MATLAB 神经网络工具箱的格式。 接下来,我们定义了一个具有10个隐藏节点的 MLP,并设置了一些训练参数(如训练轮数和学习率)。然后,我们使用训练数据集对神经网络进行训练。 最后,我们使用测试数据集对神经网络进行测试,并计算分类准确率。在这个例子中,我们得到了一个准确率约为97%的分类,这表明我们的神经网络模型能够很好地处理这个分类问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值