It turns out that hot loading is so simple. Write a Java hot loading manually

1. What is hot loading

Hot loading means that the changed code can take effect without restarting the service. Hot loading can significantly improve the efficiency of development and debugging. It is implemented by Java based class loader. However, due to the insecurity of hot loading, it is generally not used in formal production environment.

2. Difference between hot loading and hot deployment

First, whether it is hot loading or hot deployment, you can compile / deploy the project without restarting the service, which is implemented by Java based class loader.

So what's the difference between the two?

In terms of deployment mode:

In principle:

In the usage scenario:

3. Five stages of class loading

You may have found that there are seven stages in the figure, not five. Because the graph is the complete life cycle of a class. If it is just a class loading stage, the last using and unloading in the graph are not included.

Briefly describe the five stages of class loading:

It should be noted that among the five stages of class loading, only the loading stage can be customized by the user, while the validation stage, preparation stage, parsing stage and initialization stage are handled by the JVM.

4. Implement class hot loading

4.1 implementation ideas

How can we manually write the hot load of a class? According to the above analysis, when a java program is running, it will first load the class file into the JVM, and the class loading process has five stages. In the five stages, only the loading stage can be customized. Therefore, if we can change the program code and recompile, the running process can obtain the newly compiled class file in real time, Then reload, a simple java hot load can be realized in theory.

So we can get the realization idea:

4.2 custom class loader

The team designing the Java virtual machine put the class loading phase into the external implementation of the JVM (get the binary byte stream describing this class through the fully qualified name of a class). In this way, the program can decide whether to get the class information. The code module that implements this loading action is called "class loader".

In Java, the class loader is Java lang.ClassLoader. Therefore, if we want to implement a class loader ourselves, we need to inherit classloader and override the method of findclass in it, At the same time, because the class loader is implemented by the two parent delegation model (that is, except for the top-level class loader, each class loader must have a parent loader, and when loading, we will first ask whether the parent loader can be loaded, and if the parent loader cannot be loaded, we will try to load it ourselves), so we also need to specify the parent loader.

Finally, according to the class path passed in, the code to load the class is shown below.

package net.codingme.@R_810_2419@.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

/**
 * <p>
 * 自定义 java类加载器来实现Java 类的热加载
 *
 * @Author niujinpeng
 * @Date 2019/10/24 23:22
 */
public class MyClasslLoader extends ClassLoader {

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

    public MyClasslLoader(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,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;
    }
}

4.3 define the class to be hot loaded

Let's assume that a method (logic) under an interface (basemanager. Java) needs to be hot loaded.

First, define the interface information.

package net.codingme.@R_810_2419@.classloader;

/**
 * <p>
 * 实现这个接口的子类,需要动态更新。也就是热加载
 *
 * @Author niujinpeng
 * @Date 2019/10/24 23:29
 */
public interface BaseManager {

    public void logic();
}

Write an implementation class for this interface.

package net.codingme.@R_810_2419@.classloader;

import java.time.LocalTime;

/**
 * <p>
 * BaseManager 这个接口的子类要实现类的热加载功能。
 *
 * @Author niujinpeng
 * @Date 2019/10/24 23:30
 */
public class MyManager implements BaseManager {

    @Override
    public void logic() {
        System.out.println(LocalTime.Now() + ": java类的热加载");
    }
}

What we need to do later is to enable this class to be custom loaded through our myclassloader. Class hot loading should only be performed after the class information is changed and recompiled. Therefore, in order to load meaninglessly, we need to judge whether the class has been updated, so we need to record the modification time of the class and the corresponding class information.

Therefore, a class is compiled to record the modification time of a class loader corresponding to a class and the last loaded class.

package net.codingme.@R_810_2419@.classloader;

/**
 * <p>
 * 封装加载类的信息
 *
 * @Author niujinpeng
 * @Date 2019/10/24 23:32
 */
public class LoadInfo {

    /** 自定义的类加载器 */
    private MyClasslLoader myClasslLoader;

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

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

    public LoadInfo(MyClasslLoader myClasslLoader,long loadTime) {
        this.myClasslLoader = myClasslLoader;
        this.loadTime = loadTime;
    }

    public MyClasslLoader getMyClasslLoader() {
        return myClasslLoader;
    }

    public void setMyClasslLoader(MyClasslLoader myClasslLoader) {
        this.myClasslLoader = myClasslLoader;
    }

    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;
    }
}

4.4 hot loading to obtain class information

In the implementation idea, we know that the rotation training checks whether the class file has been updated, so every time we call the class to be hot loaded, we have to check whether the class has been updated and decide whether to reload it. In order to facilitate the acquisition operation in this step, a simple factory mode can be used for encapsulation.

Note that the full path needs to be specified to load the class file, so class is defined in the class_ Path constant.

package net.codingme.@R_810_2419@.classloader;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 加载 manager 的工厂
 *
 * @Author niujinpeng
 * @Date 2019/10/24 23:38
 */
public class ManagerFactory {

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

    /** 要加载的类的 classpath */
    public static final String CLASS_PATH = "D:\\IdeaProjectMy\\lab-notes\\target\\classes\\";

    /** 实现热加载的类的全名称(包名+类名 ) */
    public static final String MY_MANAGER = "net.codingme.@R_810_2419@.classloader.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) {
        MyClasslLoader myClasslLoader = new MyClasslLoader(className);
        Class loadClass = null;
        // 加载
        try {
            loadClass = myClasslLoader.loadClass(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        BaseManager manager = newInstance(loadClass);
        LoadInfo loadInfo = new LoadInfo(myClasslLoader,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.5 hot load test

Directly write a thread, constantly detect whether the class to be hot loaded has changed and needs to be reloaded, and then run the test.

package net.codingme.@R_810_2419@.classloader;

/**
 * <p>
 *
 * 后台启动一条线程,不断检测是否要刷新重新加载,实现了热加载的类
 * 
 * @Author niujinpeng
 * @Date 2019/10/24 23:53
 */
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();
            }
        }
    }
}

Main thread:

package net.codingme.@R_810_2419@.classloader;

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

The code is all ready. In the last step, you can start the test. If you are using eclipse, just start it directly; If it is idea, you need to start in debug mode (idea has certain restrictions on hot loading).

After startup, you can see the continuous output of the console:

00:08:13.018: java类的热加载
00:08:15.018: java类的热加载

At this time, we can change the output of the logic method of the mymanager class and save it.

@Override
public void logic() {
     System.out.println(LocalTime.Now() + ": java类的热加载 Oh~~~~");
}

You can see that the output of the console has changed automatically (idea needs to press Ctrl + F9 after changing).

The code has been put into GitHub: https://github.com/niumoo/lab-notes/

< end >

Personal website: https://www.codingme.net If you love this article, you can pay attention to the official account. Attention to official account recovery resources can not get the most popular Java core knowledge of the whole network: interview information.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>