轻量级 ioc 框架 loveqq,支持接口上传 jar 格式的 starter 启动器并支持热加载其中的 bean

轻量级 ioc 框架 loveqq,支持接口上传 jar 格式的 starter 启动器并支持热加载其中的 bean

热加载 starter 启动器代码示例:

java 复制代码
package com.kfyty.demo;

import com.kfyty.loveqq.framework.boot.K;
import com.kfyty.loveqq.framework.boot.context.ContextRefresher;
import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
import com.kfyty.loveqq.framework.core.autoconfig.annotation.BootApplication;
import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
import com.kfyty.loveqq.framework.core.autoconfig.condition.annotation.ConditionalOnMissingBean;
import com.kfyty.loveqq.framework.core.lang.JarIndexClassLoader;
import com.kfyty.loveqq.framework.core.utils.IOC;
import com.kfyty.loveqq.framework.web.core.annotation.GetMapping;
import com.kfyty.loveqq.framework.web.core.annotation.RequestMapping;
import com.kfyty.loveqq.framework.web.core.annotation.RestController;
import com.kfyty.loveqq.framework.web.core.autoconfig.annotation.EnableWebMvc;
import com.kfyty.loveqq.framework.web.core.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.security.cert.Extension;
import java.util.Collections;
import java.util.UUID;
import java.util.jar.JarFile;

@Slf4j
@EnableWebMvc
@RestController
@BootApplication
@RequestMapping(expose = true)                  // 自动暴露 public 方法为 POST http 接口
public class Main {
    @Autowired
    private Extension extension;

    /**
     * 测试接口
     */
    @GetMapping
    public String sayHello() {
        return extension.getId();
    }

    /**
     * 加载插件
     *
     * @param jar jar 包 启动器
     * @return 上传后的 jar 包绝对路径,卸载启动器时需要提供该返回值
     */
    public String loadPlugin(MultipartFile jar) throws Exception {
        // 保存到本地
        String filePath = "D:\\temp\\jar\\" + UUID.randomUUID().toString().replace("-", "") + "\\" + jar.getOriginalFilename();
        File jarFile = new File(filePath);
        jar.transferTo(jarFile);

        // 添加到框架 ClassLoader
        JarIndexClassLoader classLoader = (JarIndexClassLoader) IOC.class.getClassLoader();
        classLoader.addJarIndex(Collections.singletonList(new JarFile(jarFile)));

        // 刷新上下文
        ContextRefresher.refresh(IOC.getApplicationContext());

        return jarFile.getAbsolutePath();
    }

    /**
     * 卸载启动器
     *
     * @param jarPath {@link #loadPlugin(MultipartFile)} 的返回值
     */
    public String unloadPlugin(String jarPath) throws Exception {
        // 构建 File 对象
        File jarFile = new File(jarPath);

        // 从框架 ClassLoader 移除
        JarIndexClassLoader classLoader = (JarIndexClassLoader) IOC.class.getClassLoader();
        classLoader.removeJarIndex(Collections.singletonList(new JarFile(jarFile)));

        // 刷新上下文
        ContextRefresher.refresh(IOC.getApplicationContext());

        return "ok";
    }

    public static void main(String[] args) throws Exception {
        K.run(Main.class, args);
    }

    /**
     * 默认实现
     */
    @Component
    @ConditionalOnMissingBean(Extension.class)
    public static class DefaultExtension implements Extension {

        @Override
        public String getId() {
            return "default";
        }

        @Override
        public boolean isCritical() {
            return false;
        }

        @Override
        public byte[] getValue() {
            return new byte[0];
        }

        @Override
        public void encode(OutputStream out) throws IOException {

        }
    }
}

然后,新建一个项目,添加如下类:

java 复制代码
package com.kfyty.graal.example;

import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;

import java.io.IOException;
import java.io.OutputStream;
import java.security.cert.Extension;

/**
 * 动态加载示例实现
 */
@Component
public class ExampleExtension implements Extension {

    @Override
    public String getId() {
        return "example";
    }

    @Override
    public boolean isCritical() {
        return false;
    }

    @Override
    public byte[] getValue() {
        return new byte[0];
    }

    @Override
    public void encode(OutputStream out) throws IOException {

    }
}

并在 k.factories 中添加:

properties 复制代码
com.kfyty.loveqq.framework.core.autoconfig.annotation.EnableAutoConfiguration=com.kfyty.graal.example.ExampleExtension

然后打成 jar 包,就是一个启动器了。

接着启动第一段代码的 main 方法后:

先访问:http://localhost:8080/sayHello,将返回 default

然后使用 postman 上传启动器 jar 包:http://127.0.0.1:8080/loadPlugin,此时将动态加载上传的启动器,并刷新 ioc 容器

然后再访问:http://localhost:8080/sayHello,将返回 example,原因是加载了新的启动器,条件注解生效,实现类变化了!

然后再访问:http://127.0.0.1:8080/unloadPlugin,将第二步的返回值作为入参传入,此时将卸载启动器,并刷新 ioc 容器

然后再访问:http://localhost:8080/sayHello,将返回 default,原因是卸载了之前加载的启动器,条件注解生效,实现类又变化了!

从而实现了启动器的热加载,感兴趣的同学可以试一下。

gitee/github/gitcode: loveqq-framework

相关推荐
w_zero_one1 分钟前
链表(2)
java·数据结构·链表
梦幻通灵8 分钟前
IDEA导入外部Jar包进行反编译
pycharm·intellij-idea·jar
白杨尚青9 分钟前
C++入门篇(十一):string(中)——容量与增删改查:一篇吃透所有常用接口(万字详解)
java·开发语言·c++
老三牛擦12 分钟前
掌握数据结构及算法、计算机组成原理、操作系统、计算机网络和软件工程基础知识,践行Scrum敏捷开发理念
jvm
SimonKing19 分钟前
SSE、WebSocket 连接丢 Redis 里?那可踩大坑了!
java·后端·程序员
倔强的石头10624 分钟前
【Linux指南】动静态库系列(九):动态库如何进入进程地址空间:从磁盘 .so 到共享内存映射
java·linux·服务器
TeamDev30 分钟前
JxBrowser 9.5.2 版本发布啦!
java·前端·windows·chromium·混合应用·jxbrowser·浏览器控件
鬼手点金32 分钟前
Claude Code示范案例-进阶学习路径
java·服务器·前端·学习·计算机视觉·前向传播
浪子明X33 分钟前
用 CMake Presets 固定跨平台构建:从本地开发到 CI 的依赖一致性实践
java·spring·ci/cd
苏supper1 小时前
记一次Nacos鉴权报错unknown user,403排查|SecurityProxy源码,fastjson2扩展包缺失
java·后端