How to Hot-Load JAR Plugins in Spring Boot with Dynamic Bean Registration
This article explains how to dynamically load JAR plugins in a Spring Boot application using URLClassLoader, share the class loader with the main context, and register plugin classes as Spring beans both at startup and at runtime, enabling seamless extension without server restarts.
Background
Dynamic plugin programming decouples business functionality, improves maintainability, enhances extensibility, and allows features to be added without restarting the server. Traditional solutions like SPI or OSGi do not integrate plugins with Spring IoC, preventing bean injection from the main application.
This article introduces a method to hot‑load JAR files in a Spring Boot project, register them as beans, and inject main‑application beans into plugins for richer functionality.
Hot Loading JAR Files
By specifying a URL or file path, a JAR can be loaded at runtime using URLClassLoader and its addURL method. Example implementation:
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 the URLClassLoader, the system class loader must be set as its parent to bridge the main application and plugin class loaders, avoiding ClassNotFoundException during bean registration.
Dynamic Bean Registration
Plugin classes loaded from the JAR can be registered into Spring's IoC container, and existing beans from the main application can be injected into the plugin. This can be done at application startup or during runtime.
Startup Bean Registration
Use ImportBeanDefinitionRegistrar to register plugin beans when Spring Boot starts:
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);
}
}Runtime Bean Registration
Use the ApplicationContext to register beans while the application is running:
@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");
}The helper SpringUtil implements ApplicationContextAware to expose registration and retrieval methods:
@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);
}
}Summary
The approach shares a ClassLoader and dynamically registers beans, opening communication between plugins and the main program. This enables plugins to inject main‑application beans such as Redis or DataSource and call remote services.
However, because plugins share the same ClassLoader, class and version conflicts may arise, and loaded classes cannot be unloaded unless their names or paths change.
Therefore, this solution is best suited for scenarios with a small number of well‑tested plugins, clear development standards, and where plugins are packaged and released after thorough testing.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Cognitive Technology Team
Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
