如何构建回测系统(九)

如何构建回测系统(九)

关于HotReload Classloader的一个实践

在(八)中,我们主要考虑到了一个大的框架是怎么实现,大概有哪些部件,现在来看一下我的参考实现

@Slf4j
public class DBStrategyClassLoader extends ClassLoader {

    private final Map<String, Class<?>> loadClassMap;
    private static DynamicStrategyRepository repository;
    private static DBStrategyClassLoader instance;
    private static ClassLoader springbootClassloader;

    private DBStrategyClassLoader() {
        this.loadClassMap = new ConcurrentHashMap<>();
    }

    public static void init(DynamicStrategyRepository repository,
                            ClassLoader springbootClassloader) {
        DBStrategyClassLoader.repository = repository;
        DBStrategyClassLoader.springbootClassloader = springbootClassloader;
        log.info("DBStrategyClassLoader initialized");
    }

    public void reload() {
        log.info("class reload start");
        synchronized (DBStrategyClassLoader.class) {
            var newInstance = new DBStrategyClassLoader();
            var loadClassName = loadClassMap.keySet();
            for (var clazzName : loadClassName) {
                try {
                    newInstance.loadClassMap.put(clazzName, newInstance.loadClass(clazzName));
                } catch (ClassNotFoundException e) {
                    log.error("reload clazz {}: failed,", clazzName, e);
                }
            }
            instance = newInstance;
        }
        log.info("class reloaded ");
    }

    public static DBStrategyClassLoader getInstance() {
        if (Objects.isNull(instance)) {
            synchronized (DBStrategyClassLoader.class) {
                if (Objects.isNull(instance)) {
                    instance = new DBStrategyClassLoader();
                }
            }
        }
        return instance;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            return springbootClassloader.loadClass(name);
        } catch (ClassNotFoundException e) {
            return super.loadClass(name);
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        var clazz = this.loadClassMap.get(name);
        if (Objects.isNull(clazz)) {

            var dbData = repository.findByDbClazz_name(name);
            if (!dbData.isEmpty()) {
                var data = dbData.stream().max(Comparator.comparing(DynamicStrategy::getVersion))
                        .get().getDbClazz().compile();
                clazz = defineClass(name, data, 0, data.length);
                loadClassMap.put(name, clazz);
            } else {
                throw new ClassNotFoundException();
            }
        }
        return clazz;
    }
}

由谁管理这个ClassLoader的生命周期

这里有个前置条件,我大部分的组件,为了方便再springmvc中使用,都采用了Spring来做生命周期管理,但是对于classloader,为了完整将classloader在root object上置为null,应交由自己管理

故而在这里使用了双锁单例模式

public static DBStrategyClassLoader getInstance() {
    if (Objects.isNull(instance)) {
        synchronized (DBStrategyClassLoader.class) {
            if (Objects.isNull(instance)) {
                instance = new DBStrategyClassLoader();
            }
        }
    }
    return instance;
}

reload详解

为了使得reload之后,代码能重新运行,还要重新loadclass,当所有准备就绪后,替换instance

同时使用了该classloader的部件应该重新加载Class使得新的代码生效

public void reload() {
    log.info("class reload start");
    synchronized (DBStrategyClassLoader.class) {
        var newInstance = new DBStrategyClassLoader();
        var loadClassName = loadClassMap.keySet();
        for (var clazzName : loadClassName) {
            try {
                newInstance.loadClassMap.put(clazzName, newInstance.loadClass(clazzName));
            } catch (ClassNotFoundException e) {
                log.error("reload clazz {}: failed,", clazzName, e);
            }
        }
        instance = newInstance;
    }
    log.info("class reloaded ");
}

其他

由于项目是SpringBoot的项目,众所周知是Springboot自己也实现了Classloader,所有我们非数据库代码都是通过该Classloader加载的,故而,我们还需要在对象中设置Springboot的Classloader,从任意Spring管理对象然后getClass().getClassloader()即可获得