转载

JAVA基础(三)ClassLoader实现热加载

JAVA基础(三)ClassLoader实现热加载
Java极客  |  作者  /  铿然一叶
这是 Java极客 的第 63 篇原创文章

相关阅读:

JAVA基础(一)简单、透彻理解内部类和静态内部类

JAVA基础(二)内存优化-使用Java引用做缓存

1. 应用场景

  1. 修复bug,不需要重启服务,动态加载修改的bug类。
  2. 动态升级,在android系统中,可以通过动态加载APK绕过应用市场的的升级策略,自行定制升级策略。

2. 例子

网上描述ClassLoader加载的文章很多,这里不再详细描述,需要注意的是:将需要动态加载的类放到独立的jar文件中,从一开始就通过动态加载方式加载,不要放到主进程的jar包中,那样会被默认加载器加载,会导致在更新后无法重新加载。

2.1. 主项目代码/模块

此模块放不需要动态加载的类。

2.1.1. HotClassLoader.java

用于实现动态记载功能。

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

// 热加载器
public class HotClassLoader {
    private static final long LOADER_INTERVAL = 3;
    // 指向动态加载module的jar文件
    private static final String HOT_UPDATE_JAR_PATH = "D://ClassLoaderDemo-Service//target//ClassLoaderDemo-Service-1.0-SNAPSHOT.jar";

    static URLClassLoader classLoader;         //类加载器
    private static long lastModifiedTime = 0;  // jar文件最后更新时间

    // 开始监听jar文件是否有更新
    public void startListening() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(()->{
            if (isHotUpdate()) {
                reload();
            }
        }, 0, LOADER_INTERVAL, TimeUnit.SECONDS);
    }

    // 动态获取新实例,注意返回值可能为null,调用者要加判断
    public static Object newInstance(String className) {
        if (classLoader != null) {
            try {
                synchronized (HotClassLoader.class) {
                    Object newInstance = Class.forName(className, true, classLoader).newInstance();
                    return newInstance;
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    // 判断是否有更新
    private boolean isHotUpdate() {
        File hotLoaderFile = new File(HOT_UPDATE_JAR_PATH);
        boolean isHotUpdate = false;
        if (hotLoaderFile.exists()) {
            long newModifiedTime = hotLoaderFile.lastModified();
            isHotUpdate = lastModifiedTime != newModifiedTime;
            lastModifiedTime = newModifiedTime;
        } else {
            System.out.println(hotLoaderFile.getAbsolutePath() + " is not found.");
        }

        System.out.println("isHotUpdate:" + isHotUpdate);
        return isHotUpdate;
    }

    // 重新加载jar文件
    private void reload() {
        File jarPath = new File(HOT_UPDATE_JAR_PATH);
        System.out.println("jar lastModified xxxxxxxxxxxxxxxxxx: " + jarPath.lastModified());

        if (jarPath.exists()) {
            try {
                synchronized (HotClassLoader.class) {
                    classLoader = new URLClassLoader(new URL[]{jarPath.toURI().toURL()});
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Hot update jar is not found.");
        }
    }
}
复制代码

2.1.2. Service.java

模拟的动态加载类接口。

package com.javageektour.classloaderdemo;

public interface Service {
    void printVersion();
}
复制代码

2.1.3. HotLoadTest.java

测试类。

package com.javageektour.classloaderdemo;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class HotLoadTest {
    public static void main(String[] args) {
        HotClassLoader hotClassLoader = new HotClassLoader();
        hotClassLoader.startListening();

        // 休眠一会,等加载完
        sleep(3000);
        mockCaller();
        sleep(50000000);
    }

    // 模拟调用者
    private static void mockCaller() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(()->{
            try {
                Service mockService = (Service) HotClassLoader.newInstance("com.javageektour.classloaderdemo.MockService");
                mockService.printVersion();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, 0, 5, TimeUnit.SECONDS);
    }

    private static void sleep(long timeMillis) {
        try {
            Thread.sleep(timeMillis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
复制代码

2.2. 动态加载项目/模块

需要动态加载的类放到这个模块中,甚至可以按照业务划分多个模块。

注意这个module需要编译依赖主模块的接口类。

2.2.1. MockService.java

package com.javageektour.classloaderdemo;

public class MockService implements Service {
    @Override
    public void printVersion() {
        System.out.println("11.0");
    }
}
复制代码

2.3. 验证过程

1.编译动态加载模块,生成jar文件,并修改主模块中测试程序的jar文件路径。

2.启动测试demo,待打印出版本号后,修改MockService.java中的版本号重新生成jar文件。

3.等待一会打印新的版本号。

输出日志:

isHotUpdate:true
jar lastModified xxxxxxxxxxxxxxxxxx: 1587832288706
isHotUpdate:false
1.0
isHotUpdate:false
1.0
isHotUpdate:false
isHotUpdate:true
jar lastModified xxxxxxxxxxxxxxxxxx: 1587832303617
2.0
isHotUpdate:false
复制代码
原文  https://juejin.im/post/5ea45fe06fb9a03c37305788
正文到此结束
Loading...