一些近况

一些近况

关于技术

一般的小公司,没必要纠结上各种高大上的分布式。耗费的成本远超当初划分的成本,老老实实用单体应用做好代码切割才是正途
另一方面,觉得这里没有技术,或者说什么叫技术:最后变成了我回归了数学。即便依旧学不懂数学。

关于管理

大部分时候我们需要下属的目标与管理者的目标要一致。目标一致那么,制定计划的时候才能群策群力。
但是现在大部分时候都是,目标不那么一致,事情大概率是推动不下去的。想了很多缩减整改的方案,最终还是放弃了,费时费力还不讨好,不一定有成效。有成效也不能提升部门绩效。

关于生活

大头可爱了,也闹腾了。

证券、信息聚合系统框架与搭建(三)

证券、信息聚合系统框架与搭建

问题

  1. 现有的所有的渲染都放在客户端,服务端只有数据
  2. 客户端为了新增图表都需要更新客户端
  3. 目标用户更新客户端意图不强烈

扩大视角范围

不再只关注渲染层的事项,关注整个富客户端的架构

参考

参考后台开发组件化的趋势,做一个大而完整的软件平台

考虑到是软件平台,必须要解耦所有组件之间的直接调用关系

基础组件构成

  • 组件注册中心
  • 组件间通信工具(消息队列、消息总线)
  • 本地数据库
  • 渲染容器
  • 与行情交易柜台对接的各容器适配器

软件组件划分

高频变动数据,低变更

原生程序开发

低频数据

使用Chromium做Web开发,降低开发成本,同时因为Web软件的天然容易更新的属性,能够在变更时候不需要客户端更新基础组件

组件Example

证券,信息聚合前端框架设计与搭建(二)

证券,信息聚合前端框架设计与搭建(二)

技术选型

现有的React可能比较符合需求,但react的diff&patch是O(n^3)级别的算法,这里可能会限制我们单页Component数量

UI界面的话,可以任意选用各个大厂的UI,例如ant.design(PC)ele.me(手机H5)

另外可以自然的引入React Native

但不应该绑死在React技术栈上,防止其授权失效直接导致业务下线

实现路径

早期验证业务阶段可以自行封装Component,系统内部采用封装后的Component来进行代码的编写(Wrap)

等开发中常见的工具类,常见的Class均稳定后,替换Component的内部实现,可以考虑重新通过开源的virtual dom库来实现

设立低时延展示组件

完全避开react的渲染逻辑

手动Bind数据,手动确认渲染时刻并将其渲染时机封装在Component下,避免当高频数据到来时CPU全部分配至JS引擎来处理差分数据合并而Render线程被阻塞

逐步封装Table,List等组件

证券,信息聚合前端框架设计与搭建

证券,信息聚合前端框架设计与搭建

主旨

工程上

  • 减少前端文件的大小
  • 方便更新版本
  • 最好是单页面应用,减少sso成本
  • 代码混淆

通信性能要求

对于高频信息,应以websocket为主,主要需要解决的问题是从服务器到达客户端的延时抖动,以及TCP的延时断线问题

对于中频信息,应以定时任务刷新即可满足

若是采用差分数据方法,可以减少网络IO消耗

高频信息(ms级别)

  • L2交易数据
  • L1行情更新

中频信息(分钟级别)

  • IM
  • 公司公告

渲染性能要求

优先级高的,高频信息,应尽可能快的渲染(60FPS)(16.67ms)内合并vDom事件然后渲染

中频信息按需渲染

应给调用者refresh接口,方便业务可以直接触发更新界面事件

系统实施

系统实施

昨天和群里面的人聊天,谈到了别人一次成功的ERP实施

大概成功的因素有那么几个

  • CEO直接支持并指定时刻表
  • 财务主管直接深度参与业务梳理并位置负责,IT部门只负责实现
  • 系统完全自研
  • 研发人员是一线直接提拔
  • 灰色地带的太极手法

从这些方面来看,企业的数字化转型不能仅仅是IT部门立个项目就可以了的,是需要整个公司链条配合起来,流转起来,数字化是深度嵌入在每个流程之中

Ta举了个例子

客户下订单,生产部门领取物料后生产完毕就发出去了,导致最后核算时候,多出一个产品

原因在于生产完毕后没有执行入库就发出去了

高层查账时候会很直接的导致一大批人要受罚,只有接触监管层,采取一个监管层能理解并且逻辑上自洽的流程才能修复

在这种灰不溜秋的时候是系统实施中最困难的时候

没有人员的深度参与是无法梳理出这种事项的

而这种情况下如果无法处理则ERP系统会导致所有人都不愿意使用,这个时候IT部门再强大也没有用,需求迭代是中断的

由此感叹道,一个事情的成功不仅仅是单一因素的成功,而是多个因素都成功才成功的

江说得好,既要自身的奋斗,也要看历史的进程;大概就可以从这个事情看出来

鸿蒙读书笔记

鸿蒙读书笔记

核心系统

kernel基本定义了线程,内存,基础数据结构的公共部分

是其他组件的基石

losbase

定义了基本的信号

定义了内存运算接口

定义了内存对齐接口

定义了休眠接口

bitmap

一些bitmap的运算

bitmap的数据结构

cpup

cpu使用量统计

err

err的IO定义于,err的定义

event

系统事件定义,及调度函数

其他

一些通用的数据结构定义

例如

  • 几种常见的锁
  • NodeList
  • mux
  • 系统类型
  • 队列
  • 进程结构

遇到事情的思考方式

遇到事情的思考方式

  • 事情有哪些问题需要解决
  • 这些问题中,哪些可以马上给出解决方案,哪些不可以
  • 可以马上给出解决方案的成本有哪些
  • 那些无法马上给出解决方案的
    • 需要哪些协助
    • 需要哪方面的协助
  • 验证最终结果,复盘

Socket编程实践(二)

Socket编程实践(二)

最近新任务,需要写一个Socket服务端,用来接收设备透传数据

考虑以下几点

  1. 设备规模(300~2000)
  2. 链接方式(长连接)
  3. 文本协议未定
  4. 有持久化需求
  5. 有前端控制页面

方案选型

  • 因为涉及有持久化需求,应该都是数据库相关操作,直接使用SpringBoot套件
  • 因为有前端控制需求,直接添加SpringMVC作为控制面板开发套件
  • 因为设备规模仅有2000个且是长连接,直接使用BIO协议开发效率更高(更推荐Netty来做开发模型,可惜不太熟悉)

类设计

以下三个类代表了领域模型核心,其他功能均基于以下三个对象做开发或辅助

Device

代表了具体的一个设备

  1. 设备验证与登录
  2. 设备状态维护
  3. 设备数据接收
  4. 设备数据维护与持久化

DevicePool

代表了设备们的管理功能,如

  1. 监测在线
  2. 监测是否有数据到达
  3. 设备上线下线处理

Waitress

代表了SocketServer接待处

仅用来分发接收到的socket然后交给线程池就不管事了

如何构建回测系统(十)

如何构建回测系统(十)

问题

在(九)中实现了一个可以从数据库中编译并加载代码的classloader,但这是有缺陷的

缺陷在于,如果要使用回测系统实现的类和对象则会出现编译无法通过

提示Symbol找不到或者缺失

定位问题

日常我们使用编译功能一般都是maven或者是ant等框架来编译,很少说直接用javac来直接对整个项目编译

在(九)使用的compiler其实就是javac的java代码版本如果采用javac编译一个引用了其他java或者class的文件,则会出现很类似的症状

找到Maven/IDE等工具具体执行javac的代码其实就能看到,是编译时候带上了参数-cp/-classpath/

解决问题

compiler的getTask函数中有个参数叫options,根据文档,这是类似于javac中的参数,但其实如果我们简单的把依赖的class按照-cp的形式添加到参数中就会发现直接报错

这是因为-cp是传给jvm的参数,getTask函数只支持具体的54个参数,属于编译期间具体的信息数据用的

而-cp产生作用的方式是在FileManager中的list()函数起作用的(这里完全没有任何文档,只能单步调试代码发现,所以这里无法说以后就一定正确)

我们只需要将依赖的class/jar/java文件按照需求变更成SimpleJavaFileObject既可

参考代码

因为环境因素,这里的参考代码是在springboot中实现的

package com.michaelssss.domain.strategy;

import static javax.tools.StandardLocation.CLASS_PATH;

import com.michaelssss.exception.RunException;
import com.michaelssss.utils.ArrayUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import org.springframework.stereotype.Component;

@Component
public class MyCompiler {

    public byte[] compile(String name, String codes) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        var diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
        var fileManager = new MyJavaFileObjectManager(
            compiler.getStandardFileManager(diagnosticCollector, Locale.CHINESE,
                StandardCharsets.UTF_8));
        fileManager.complieName = name;
        var javaFileObjects = Collections
            .singletonList(
                new MyJavaFileObject(name.replace('.', '/'), codes, fileManager.outputStream));
        var options = new ArrayList<String>();
        var compilerTask = compiler
            .getTask(null, fileManager, diagnosticCollector, options, null, javaFileObjects);
        var result = compilerTask.call();
        if (Objects.nonNull(result) && result) {
            return fileManager.outputStream.toByteArray();
        } else {
            StringBuilder stringBuilder = new StringBuilder();
            diagnosticCollector.getDiagnostics()
                .forEach(diagnostic -> stringBuilder.append(
                    diagnostic.toString()
                ).append("\r\n"));
            throw new RunException(stringBuilder.toString());
        }
    }

    static class MyJavaFileObjectManager extends ForwardingJavaFileManager<JavaFileManager> {

        Map<String, JavaFileObject> fileObjects = new HashMap<>();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        List<JavaFileObject> result;
        String complieName;

        public MyJavaFileObjectManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        @Override
        public JavaFileObject getJavaFileForInput(Location location, String className,
            Kind kind) throws IOException {
            JavaFileObject javaFileObject = fileObjects.get(className);
            if (!location.getName().contains(CLASS_PATH.getName())) {
                return super.getJavaFileForInput(location, className, kind);
            }
            if (Objects.isNull(javaFileObject)) {
                if (Objects.isNull(result)) {
                    result = CompilerDependenceResolver.resolve();
                }
                var aa = result.stream().filter(
                    f -> f.getName().equalsIgnoreCase(className) && kind.equals(f.getKind()))
                    .collect(
                        Collectors.toList());
                if (aa.isEmpty()) {
                    return super.getJavaFileForInput(location, className, kind);
                } else {
                    return aa.get(0);
                }
            }
            return javaFileObject;
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName,
            JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            JavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind,
                outputStream);
            fileObjects.put(qualifiedClassName, javaFileObject);
            return javaFileObject;
        }

        @Override
        public String inferBinaryName(Location location, JavaFileObject file) {
            if (file instanceof MyJavaFileObject) {
                return file.getName();
            }
            if (file instanceof WithPackageAccessSimplaJava) {
                return ((WithPackageAccessSimplaJava) file).getBinaryName();
            }
            if (
                file instanceof NewJarJavaFileObject
            ) {
                return ((NewJarJavaFileObject) file).getBinaryName();
            } else {
                return super.inferBinaryName(location, file);
            }
        }

        @Override
        public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds,
            boolean recurse) throws IOException {

            if (location.getName().contains(CLASS_PATH.getName())) {
                if (Objects.isNull(result)) {
                    result = CompilerDependenceResolver.resolve();
                }
                if (!result.isEmpty()) {
                    var a = result.stream()
                        .filter(
                            r -> ArrayUtils
                                .isContain(((MySimpleJavaObjectI) r).getBinaryName().split("\\."),
                                    packageName.split("\\.")) && kinds
                                .contains(r.getKind()))
                        .collect(Collectors.toList());
                    var e = super.list(location, packageName, kinds, recurse);
                    e.forEach(a::add);
                    return a;
                }
            }
            return super.list(location, packageName, kinds, recurse);
        }
    }


    static class MyJavaFileObject extends SimpleJavaFileObject {

        String source;
        OutputStream outputStream;
        String name;

        public MyJavaFileObject(String name, Kind kind, OutputStream stream) {
            super(URI.create("String:///" + name + kind.extension), kind);
            this.outputStream = stream;
        }

        public MyJavaFileObject(String name, String source) {
            super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
            this.source = source;
            this.name = name;
        }

        public MyJavaFileObject(String name, String source, OutputStream outputStream) {
            this(name, source);
            this.outputStream = outputStream;
        }

        public MyJavaFileObject(URI uri, Kind kind) {
            super(uri, kind);
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return source;
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            return outputStream;
        }
    }
}

package com.michaelssss.domain.strategy;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.system.ApplicationHome;

public class CompilerDependenceResolver {

    /**
     * @return 返回所有非SystemLoader加载的class的JavaFileObject
     */
    public static List<String> getClassPath() {
        var systemType = System.getProperty("os.name");

        var classpath = new String[0];
        if (systemType.contains("Windows")) {
            classpath = System.getProperty("java.class.path").split(";");
        }
        if (systemType.contains("Linux")) {
            classpath = System.getProperty("java.class.path").split(":");
        }
        return Arrays.asList(classpath);
    }

    @SneakyThrows
    public static List<JavaFileObject> resolve() {
        List<JavaFileObject> result = new ArrayList<>();
        //查询所有非springboot主文件本身的jar
        final List<String> jarlibs = new ArrayList<>();
        var loadPath = System.getProperty("loader.path");
        var classpath = System.getProperty("java.class.path").split(";");
        if (StringUtils.isNotEmpty(loadPath)) {
            classpath = Files.list(Path.of(loadPath))
                .filter(path -> !Files.isDirectory(path) && path.toString().endsWith(".jar"))
                .map(Path::toString).toArray(String[]::new);
        }
        jarlibs.addAll(Arrays.stream(classpath).filter(s -> s.endsWith(".jar"))
            .collect(Collectors.toList()));
        var compilerModuleClassPaths = Arrays.stream(classpath).filter(s -> !s.endsWith(".jar"))
            .collect(Collectors.toList());

        if (jarlibs.isEmpty()) {
            throw new Error("load jar info failed");
        }
        jarlibs.forEach(s -> {
            try {
                JarFile jarFile = new JarFile(s);
                jarFile
                    .stream()
                    .forEach(jarEntry -> {
                        if (!jarEntry.isDirectory() && jarEntry.getName().endsWith(".class")) {
                            try (var in = jarFile.getInputStream(jarEntry)) {
                                var jarPath = Path.of(s);
                                String separator = jarEntry.getName().startsWith("/") ? "!" : "!/";
                                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                                IOUtils.copy(in, outputStream);
                                result.add(
                                    new NewJarJavaFileObject(
                                        URI.create(
                                            "jar:///" + jarPath.toUri().normalize() + separator
                                                + jarEntry
                                                .getName()),
                                        Kind.CLASS,
                                        outputStream,
                                        jarEntry
                                    ));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });

            } catch (IOException e) {
                e.printStackTrace();
            }

        });
        if (!isJar()) {
            var path = Path.of(getPath());
            Files.walkFileTree(path, new FileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                    throws IOException {
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                    throws IOException {
                    if (file.toString().endsWith(".class")) {

                        WithPackageAccessSimplaJava withPackageAccessSimplaJava = new WithPackageAccessSimplaJava(
                            path.toUri(),
                            Kind.CLASS, path.normalize().toString(), file);
                        result.add(withPackageAccessSimplaJava);
                    }
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc)
                    throws IOException {
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                    throws IOException {
                    return FileVisitResult.CONTINUE;
                }
            });
            compilerModuleClassPaths.forEach(path1 -> {
                try {
                    Files.walkFileTree(Path.of(path1), new FileVisitor<Path>() {
                        @Override
                        public FileVisitResult preVisitDirectory(Path dir,
                            BasicFileAttributes attrs)
                            throws IOException {
                            return FileVisitResult.CONTINUE;
                        }

                        @Override
                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                            throws IOException {
                            if (file.toString().endsWith(".class")) {
                                WithPackageAccessSimplaJava withPackageAccessSimplaJava = new WithPackageAccessSimplaJava(
                                    Path.of(path1).toUri(),
                                    Kind.CLASS, path1, file);
                                result.add(withPackageAccessSimplaJava);
                            }
                            return FileVisitResult.CONTINUE;
                        }

                        @Override
                        public FileVisitResult visitFileFailed(Path file, IOException exc)
                            throws IOException {
                            return FileVisitResult.CONTINUE;
                        }

                        @Override
                        public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                            throws IOException {
                            return FileVisitResult.CONTINUE;
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            var springbootjarpath = Arrays.stream(System.getProperty("java.class.path").split(";"))
                .filter(s -> s.endsWith(".jar"));
            var tempPath = Path.of(getPath()).getParent().resolve("temp");
            if (Files.notExists(tempPath)) {
                Files.createDirectories(tempPath);
            }
            springbootjarpath.forEach(s -> {
                try {
                    //直接解压

                    var jarFile = new JarFile(s);
                    jarFile.stream().filter(jarEntry -> jarEntry.getName().contains("BOOT-INF"))
                        .filter(jarEntry -> !jarEntry.isDirectory())
                        .forEach(jarEntry -> {
                            try {
                                if (Files
                                    .notExists(tempPath.resolve(jarEntry.getName()).getParent())) {
                                    Files.createDirectories(
                                        tempPath.resolve(jarEntry.getName()).getParent());
                                }

                                Files.deleteIfExists(tempPath.resolve(jarEntry.getName()));
                                var filePath = Files
                                    .createFile(tempPath.resolve(jarEntry.getName()));

                                var ou = Files.newOutputStream(filePath);
                                var in = jarFile.getInputStream(jarEntry);
                                IOUtils.copy(in, ou);
                                in.close();
                                ou.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        });

                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            Files.walk(tempPath.resolve("BOOT-INF"))
                .filter(f -> !Files.isDirectory(f))
                .forEach(s -> {
                    if (s.toString().endsWith(".jar")) {
                        try {
                            JarFile jarFile = new JarFile(s.toString());
                            jarFile
                                .stream()
                                .forEach(jarEntry -> {
                                    if (!jarEntry.isDirectory() && jarEntry.getName()
                                        .endsWith(".class")) {
                                        try (var in = jarFile.getInputStream(jarEntry)) {
                                            var jarPath = Path.of(s.toString());
                                            String separator =
                                                jarEntry.getName().startsWith("/") ? "!" : "!/";
                                            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                                            IOUtils.copy(in, outputStream);
                                            result.add(
                                                new NewJarJavaFileObject(
                                                    URI.create(
                                                        "jar:///" + jarPath.toUri().normalize()
                                                            + separator
                                                            + jarEntry
                                                            .getName()),
                                                    Kind.CLASS,
                                                    outputStream,
                                                    jarEntry
                                                ));
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                });

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else if (s.toString().endsWith(".class")) {
                        WithPackageAccessSimplaJava withPackageAccessSimplaJava = new WithPackageAccessSimplaJava(
                            s.toUri(),
                            Kind.CLASS,
                            tempPath.resolve("BOOT-INF").resolve("classes").normalize().toString(),
                            s);
                        result.add(withPackageAccessSimplaJava);
                    }
                });
        }
        return result;
    }

    public static String getPath() {
        ApplicationHome home = new ApplicationHome(DBClazz.class);
        return home.getSource().getPath();
    }

    /**
     * 判断是否jar模式运行
     */
    public static boolean isJar() {
        return StringUtils.isNotEmpty(System.getProperty("loader.path"));
    }


    public static void main(String[] args) {
        resolve();
    }
}

如何构建回测系统(九)

如何构建回测系统(九)

关于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()即可获得