乐趣区

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

一、背景

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

常见的动静插件的实现形式有 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

 

扫码关注有惊喜!

退出移动版