关于java:Spring-Boot-如何热加载jar实现动态插件

48次阅读

共计 3494 个字符,预计需要花费 9 分钟才能阅读完成。

一、背景

动静插件化编程是一件很酷的事件,能实现业务性能的 解耦 便于保护,另外也能够晋升 可扩展性 随时能够在不停服务器的状况下扩大性能,也具备十分好的 开放性 除了本人的研发人员能够开发性能之外,也能接收第三方开发商依照标准开发的插件。

常见的动静插件的实现形式有 SPIOSGI 等计划,因为脱离了 Spring IOC 的治理在插件中无奈注入主程序的 Bean 对象,例如主程序中曾经集成了 Redis 然而在插件中无奈应用。

本文次要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路,在动静扩大性能的同时反对在插件中注入主程序的 Bean 实现性能更弱小的插件。

 

二、热加载 jar 包

通过指定的链接或者门路动静加载 jar 包,能够应用 URLClassLoaderaddURL 办法来实现,样例代码如下:

ClassLoaderUtil 类

public class ClassLoaderUtil {public static ClassLoader getClassLoader(String url) {
        try {Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            if (!method.isAccessible()) {method.setAccessible(true);
            }
            URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());
            method.invoke(classLoader, new URL(url));
            return classLoader;
        } catch (Exception e) {log.error("getClassLoader-error", e);
            return null;
        }
    }
}

其中在创立 URLClassLoader 时,指定以后零碎的 ClassLoader 为父类加载器 ClassLoader.getSystemClassLoader() 这步比拟要害,用于买通主程序与插件之间的 ClassLoader,解决把插件注册进 IOC 时的各种 ClassNotFoundException 问题。

 

三、动静注册 Bean

将插件 jar 中加载的实现类注册到 Spring 的 IOC 中,同时也会将 IOC 中已有的 Bean 注入进插件中;别离在程序启动时和运行时两种场景下的实现形式。

3.1. 启动时注册 Bean

应用 ImportBeanDefinitionRegistrar 实现在 Spring Boot 启动时动静注册插件的 Bean,样例代码如下:

PluginImportBeanDefinitionRegistrar 类

public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    private final String targetUrl = "file:/D:/SpringBootPluginTest/plugins/plugin-impl-0.0.1-SNAPSHOT.jar";
    private final String pluginClass = "com.plugin.impl.PluginImpl";

    @SneakyThrows
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);
        Class<?> clazz = classLoader.loadClass(pluginClass);
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        BeanDefinition beanDefinition = builder.getBeanDefinition();
        registry.registerBeanDefinition(clazz.getName(), beanDefinition);
    }
}

 

3.2. 运行时注册 Bean

程序运行时动静注册插件的 Bean 通过应用 ApplicationContext 对象来实现,样例代码如下:

@GetMapping("/reload")
public Object reload() throws ClassNotFoundException {ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);
        Class<?> clazz = classLoader.loadClass(pluginClass);
        springUtil.registerBean(clazz.getName(), clazz);
        PluginInterface plugin = (PluginInterface)springUtil.getBean(clazz.getName());
        return plugin.sayHello("test reload");
}

SpringUtil 类

@Component
public class SpringUtil implements ApplicationContextAware {
    private DefaultListableBeanFactory defaultListableBeanFactory;
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();}

    public void registerBean(String beanName, Class<?> clazz) {BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());
    }

    public Object getBean(String name) {return applicationContext.getBean(name);
    }
}

 

四、总结

本文介绍的插件化实现思路通过 共用 ClassLoader动静注册 Bean 的形式,买通了插件与主程序之间的类加载器和 Spring 容器,使得能够十分不便的实现插件与插件之间和插件与主程序之间的 类交互,例如在插件中注入主程序的 Redis、DataSource、调用近程 Dubbo 接口等等。

然而因为没有对插件之间的 ClassLoader 进行 隔离 也可能会存在如类抵触、版本抵触等问题;并且因为 ClassLoader 中的 Class 对象无奈销毁,所以除非批改类名或者类门路,不然插件中已加载到 ClassLoader 的类是没方法动静批改的。

所以本计划比拟适宜插件数据量不会太多、具备较好的开发标准、插件通过测试后能力上线或公布的场景。

 

五、残缺 demo

https://github.com/zlt2000/springs-boot-plugin-test

 

扫码关注有惊喜!

正文完
 0