1. Background
Dynamic plug-in programming is a cool thing. The decoupling that can realize business functions is easy to maintain, and it can also improve the can be extended without stopping the server at any time, and it is also very good Openness can not only develop functions by its own R&D staff, but also accept plug-ins developed by third-party developers in accordance with specifications.
Common implementations of dynamic plug-ins include SPI
, OSGI
etc. Because it is separated from the management of Spring IOC, the Bean object of the main program cannot be injected into the plug-in. For example, the main program has integrated Redis but cannot be used in the plug-in.
This article mainly introduces an implementation idea of hot loading jar packages in Spring Boot projects and registering them as Bean objects. While dynamically expanding functions, it supports injecting the beans of the main program into plug-ins to achieve more powerful plug-ins.
Two, hot load jar package
Loading a specified dynamic link or path through the jar package, may be used URLClassLoader
the addURL
way to implement, the sample code is as follows:
ClassLoaderUtil Class
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;
}
}
}
When creating URLClassLoader
, it is critical to specify the ClassLoader of the current system as the parent class loader ClassLoader.getSystemClassLoader()
which is used to open up the ClassLoader between the main program and the plug-in, and solve various ClassNotFoundException problems when the plug-in is registered into the IOC.
Three, dynamically register Bean
Register the implementation class loaded in the plug-in jar into Spring's IOC, and at the same time inject the existing Beans in the IOC into the plug-in; the implementation methods are respectively in the two scenarios of program startup and runtime.
3.1. Register Bean at startup
Use ImportBeanDefinitionRegistrar
to dynamically register the Bean of the plug-in when Spring Boot starts. The sample code is as follows:
PluginImportBeanDefinitionRegistrar class
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. Registering Beans at runtime
The Bean of the plug-in dynamically registered when the program is running is ApplicationContext
object. The sample code is as follows:
@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 class
@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);
}
}
Four, summary
The plug-in implementation ideas introduced in this article use share ClassLoader and dynamically register Bean , which opens up the class loader and Spring container between the plug-in and the main program, making it very convenient to implement plug-ins and plug-ins. type interaction with the main program , such as injecting Redis and DataSource of the main program into the plug-in, calling the remote Dubbo interface, etc.
However, because there is no alignment between the plug ClassLoader
be isolation may also exist problems such as class conflict, version conflicts; and because of ClassLoader Class object can not be destroyed, so unless you change the class name or class path, or plug-in is loaded The class to the ClassLoader cannot be modified dynamically.
Therefore, this solution is more suitable for scenarios where the amount of plug-in data is not too much, the plug-in has good development specifications, and the plug-in can be launched or released after being tested.
Five, complete demo
https://github.com/zlt2000/springs-boot-plugin-test
Scan the QR code to pay attention to surprises!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。