共计 3494 个字符,预计需要花费 9 分钟才能阅读完成。
一、背景
动静插件化编程是一件很酷的事件,能实现业务性能的 解耦 便于保护,另外也能够晋升 可扩展性 随时能够在不停服务器的状况下扩大性能,也具备十分好的 开放性 除了本人的研发人员能够开发性能之外,也能接收第三方开发商依照标准开发的插件。
常见的动静插件的实现形式有 SPI
、OSGI
等计划,因为脱离了 Spring IOC 的治理在插件中无奈注入主程序的 Bean 对象,例如主程序中曾经集成了 Redis 然而在插件中无奈应用。
本文次要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路,在动静扩大性能的同时反对在插件中注入主程序的 Bean 实现性能更弱小的插件。
二、热加载 jar 包
通过指定的链接或者门路动静加载 jar 包,能够应用 URLClassLoader
的 addURL
办法来实现,样例代码如下:
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
扫码关注有惊喜!