美文网首页
插件管理-热加载

插件管理-热加载

作者: SingleException | 来源:发表于2022-05-13 14:30 被阅读0次

背景

    最近我司在做一个低代码平台的项目,大概就是拖拖拽拽会生成一个表单,表单可能映射到一个数据表,对表单的数据的维护数据会入到这张表里面,但是一般简单的CURD操作可能这种需求就可以满足,但是复杂的,比如这张表的数据入表了我还需要操作其他的表,或者我可能要发一个通知消息或者之类的更复杂的业务,这种后置的业务一般都是复杂且不通用的,我们就希望能够对每个不同的业务表进行这种增强处理,而且线上的话可能存在几百台服务器,我们也不想进行服务的重启就想实现这种功能。然后插件的jar包要能使用业务系统里面的ioc容器内的对象,比如feignclient,resttemplate,jdbctemplate等等。如果业务系统内有插件内要使用的类加载业务系统的,否则加载插件内的(不要打破双亲委派)。

实现

    我们设计了一个插件管理的模块,然后针对表单的curd的操作每个操作又大致分为before,after,afercommit等一些操作位置,在这些位置动态调用插件里面的class类。整个的系统交互流程


执行流程.png         但是我们知道外部的插件jar被装载之后的话,我们是没发卸载的,比如这个jar更新了,ClassLoader的loadClass方法会先去查询缓存。所以我们会为每个jar自定义一个类加载器,这样每个类之间是隔离的。即使你更新了插件,我们会认为是两个插件,之前的也保留,现在的会用一个新的classloader加载,然后覆盖掉ioc容器内的旧的内容保留最新的对象。 类加载过程.png        

主要实现代码

  • 加载外部jar
    public static PluginClassLoader loadJar(String jarPath, String[] classNames, ConfigurableApplicationContext applicationContext) {
        try {
            PluginClassLoader pluginClassLoader = new PluginClassLoader(jarPath);
            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();

            for (String className : classNames) {
                // 加载类
                Class obj = pluginClassLoader.loadClass(className);
                String beanName = getBeanNameByClassName(className);
                if (defaultListableBeanFactory.containsBean(beanName)) {
                    log.info("removeBeanDefinition: = {}  ", beanName);
                    defaultListableBeanFactory.removeBeanDefinition(beanName);
                }
                // 注册进入ioc
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(obj);
                BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
                beanDefinition.setScope("singleton");
                defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);
            }
            return pluginClassLoader;
        } catch (Exception e) {
            log.error("loadJar fail", e);
        }
        return null;
    }
  • 调用执行外部jar
 /**
     * 从 ioc获取对象   执行增强的逻辑
     *
     * @throws Exception
     */
    @Override
    public Object invokeJar(String fileName, String classPath, String method, Class c, Object args) throws Exception {
        InputStream in = null;
        OutputStream out = null;
        try {
            String localName = fileProperties.getUploadDir() + File.separator + fileName;
            File files = new File(localName);
            if (!files.exists()) {
                // 判断本地是否有   下载拉取包
                files.createNewFile();
                out = new FileOutputStream(files);
                // 保存到本地
                in = myMinioFileOperator.getFileBytes(fileName);
                IoUtil.copy(in, out);
                in.close();
                out.close();
                files = new File(localName);
                // 针对启动时更新上传的包
                PluginUtils.loadJar(localName, new String[]{classPath}, applicationContext);
            }
            // 执行
            String name = PluginUtils.getBeanNameByClassName(classPath);

            // 针对刚启动装载
            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
            if (!defaultListableBeanFactory.containsBean(name)) {
                PluginUtils.loadJar(localName, new String[]{classPath}, applicationContext);
            }


            Object config = applicationContext.getBean(name);
            if (EmptyUtils.isEmpty(args)) {
                return config.getClass().getDeclaredMethod(method).invoke(config);
            } else {
                return config.getClass().getDeclaredMethod(method, c).invoke(config, args);
            }
        } finally {
            IoUtil.close(in);
            IoUtil.close(out);
        }
    }
  • 类加载器定义
@Slf4j
public class PluginClassLoader extends SecureClassLoader {

    private String jarFilePath;

    public PluginClassLoader(String jarFilePath) {
        this.jarFilePath = jarFilePath;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 从jar 包里面查找类
        //  log.info("findClass = {}    classLoader = {}", name, name.getClass().getClassLoader());
        ByteArrayOutputStream byteArrayOutputStream = null;
        InputStream inputStream = null;
        JarFile jarFile = null;
        try {
            String path = name.replace('.', '/').concat(".class");
            String pluginUrl = "jar:file:" + jarFilePath + "!/" + path;  //  be careful
            URL url = new URL(pluginUrl);
            // InputStream inputStream = url.openStream();
            JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
            jarFile = jarURLConnection.getJarFile();
            JarEntry jarEntry = jarURLConnection.getJarEntry();
            inputStream = jarFile.getInputStream(jarEntry);


            byteArrayOutputStream = new ByteArrayOutputStream();
            int b;
            while ((b = inputStream.read()) != -1) {
                byteArrayOutputStream.write(b);
            }
            byte[] data = byteArrayOutputStream.toByteArray();
            Class cs = this.defineClass(name, data, 0, data.length);
            log.info("findClass = {}   classLoader = {}", name, cs.getClassLoader());
            return cs;
        } catch (Exception e) {
            log.error("findClass fail", e);
        } finally {
            IoUtil.close(byteArrayOutputStream);
            IoUtil.close(inputStream);
            IoUtil.close(jarFile);
        }
        return null;
    }

    // 如果本地和包中有相同的类,以jar中为准的话需要重写loadClass方法,指定加载顺序不走双亲委派机制
    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        //  log.info("loadClass = {}", name);
        return super.loadClass(name, resolve);
    }

}

相关文章

  • 插件化和热修复对资源和类加载对比分析

    插件化和热修复对资源和类加载的管理 1 插件化为什么宿主可以解析插件资源2 热修复为什么可以解析补丁资源3 插件化...

  • Android的动态加载插件

    Android的动态加载插件apk 分析 动态加载主要分为加载使用插件的资源和管理插件的Activity、serv...

  • Android拿高薪面试题必看,

    插件化、热修复 、热更新的理解 插件化 – apk 分为宿主和插件部分,插件在需要的时候才加载进来 热修复 – 更...

  • Android高薪面试题汇录

    插件化、热修复 、热更新的理解 插件化 – apk 分为宿主和插件部分,插件在需要的时候才加载进来 热修复 – 更...

  • tmux精美主题Dracula配置

    安装tpm插件管理器 首先下载tpm插件管理器 配置激活插件管理器 从新加载tmux配置 安装Dracula 将D...

  • 插件化与组件化开发

    1.插件化 [Android] 开发资料收集:动态加载、插件化、热修复技术 2.【转】Android插件化从入门到...

  • 插件化

    插件化插件化和热修复都 属于动态加载技术 动态加载技术在应用程序运行时,动态加载一些程序中原本不存在的可执行文件并...

  • Android ClassLoader源码解析

    提起热修复以及插件化,相信大家肯定不陌生,而无论是热修复还是插件化,其理论依据就是Android 类加载机制。今天...

  • Github资源收集

    动态加载/插件化/热更新 Dynamic-load-apk Android-Plugin-Framework AC...

  • 前端插件模式

    插件模式主要分为三部分 plugCore:插件内核,提供插件运行时,管理插件的加载、运行、卸载等生命周期(类比浏览...

网友评论

      本文标题:插件管理-热加载

      本文链接:https://www.haomeiwen.com/subject/mxayurtx.html