Java使用自定义类加载器实现热部署
// 2020-08-01:之前的代码 findClass 写成 loadClass 了,弄错了。
热部署:
dynamic.ClassLoader:
package dynamic;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
public class ClassLoadStudy {
    public static void main(String[] args) throws Exception {
        HotDeploy hot = new HotDeploy("dynamic.Task");
        hot.monitor();
        while (true) {
            TimeUnit.SECONDS.sleep(2);
            hot.getTask().run();
        }
    }
}
/**
 * 热部署类
 */
class HotDeploy {
    private static volatile Runnable instance;
    private final String FILE_NAME;
    private final String CLASS_NAME;
    public HotDeploy(String name) {
        CLASS_NAME = name; // 类的完全限定名
        name = name.replaceAll("\\.", "/") + ".class";
        FILE_NAME = (getClass().getResource("/") + name).substring(6); // 判断class文件修改时间使用,substring(6)去掉开头的file:/
    }
    /**
     * 获取一个任务
     * @return
     */
    public Runnable getTask() {
        if (instance == null) { // 双重检查锁,单例,线程安全
            synchronized (this) {
                if (instance == null) {
                    try {
                        instance = createTask();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return instance;
    }
    /**
     * 创建一个任务,重新加载 class 文件
     */
    private Runnable createTask() {
        try {
            Class<?> clazz = new MyClassLoader(null).loadClass(CLASS_NAME);
            if (clazz != null)
                return (Runnable)clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 监视器,监视class文件是否被修改过,如果是的话,则重新加载
     */
    public void monitor() {
        Thread t = new Thread(()->{
            try {
                long lastModified = Files.getLastModifiedTime(Path.of(FILE_NAME)).toMillis();
                while(true) {
                    TimeUnit.SECONDS.sleep(1);
                    long now = Files.getLastModifiedTime(Path.of(FILE_NAME)).toMillis();
                    if(now != lastModified) { // 如果class文件被修改过了
                        System.out.println("yes");
                        lastModified = now;
                        instance = createTask(); // 重新加载
                    }
                }
            } catch (InterruptedException | IOException e) {
                e.printStackTrace();
            }
        });
        t.setDaemon(true); // 守护进程
        t.start();
    }
    /**
     * 自定义类加载器
     */
    private class MyClassLoader extends ClassLoader {
        private MyClassLoader(ClassLoader parent) {
            super(parent);
        }
        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] b = Files.readAllBytes(Path.of(FILE_NAME));
                return defineClass(name, b, 0, b.length);
            } catch (IOException e) {
                throw new ClassNotFoundException(name);
            }
        }
    }
}
动态改变的 Task 类,dynamic.Task:
package dynamic;
public class Task implements Runnable {
    @Override
    public void run() {
        System.out.print("=> ");
    }
}
- 
- 
URL getClass.getResource(String path)
- 
InputStream getClass().getResourceAsStream(String path)
- 
getResource("")返回当前类所在的包的路径
- 
getResource("/")返回当前的 classpath 根据路径
 
- 
- 
path 不能以 /开始,path 是从 classpath 根开始算的, 因为classloader 不是用户自定义的类,所以没有相对路径的配置文件可以获取,所以默认都是从哪个classpath 路径下读取,自然就没有必要以/开头了 。- 
URL Class.getClassLoader().getResource(String path)
- 
InputStream Class.getClassLoader().getResourceAsStream(String path)
 
- 
 
                    
                     
                    
                 
                    
                

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号