Hello!
I now want to dynamically load and unload plugins so that the main project can access the beans and APIs in the plugins.
My loading code is as follows:
public void registerPlugin(Path pluginPath) {
SpringPlugin plugin = null;
String pluginId = null;
try {
pluginId = pluginManager.loadPlugin(pluginPath);
pluginManager.startPlugin(pluginId);
plugin = (SpringPlugin) pluginManager.getPlugin(pluginId).getPlugin();
ApplicationContext pluginApplicationContext = plugin.getApplicationContext();
String[] beanNames = pluginApplicationContext.getBeanDefinitionNames();
for (String beanName : beanNames) {
Object bean = pluginApplicationContext.getBean(beanName);
if (bean.getClass().isAnnotationPresent(Controller.class) || bean.getClass().isAnnotationPresent(RestController.class)) {
registerController(bean);
}
}
} catch (Exception e) {
//加载插件失败要将所有的bea和controller注销
unloadPlugin(pluginId,false);
log.error("插件动态加载失败:", e);
}
}
public void registerController(Object bean) {
RequestMappingHandlerMapping mappingHandlerMapping = mainApplicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
RequestMappingInfo classMappingInfo = getClassMappingInfo(bean);
for (Method method : bean.getClass().getMethods()) {
if (!isHandlerMethod(method)) {
continue;
}
RequestMappingInfo mappingInfo = getMethodMappingInfo(method);
// 合并类级别的路径前缀与方法级别的 RequestMappingInfo
RequestMappingInfo finalMappingInfo = (classMappingInfo != null && mappingInfo != null)
? classMappingInfo.combine(mappingInfo)
: mappingInfo;
if (finalMappingInfo != null) {
mappingHandlerMapping.registerMapping(finalMappingInfo, bean, method);
log.info("方法 {} 成功注册, 注册路径:{}", method.getName(), finalMappingInfo.getPatternsCondition());
}
}
}
private RequestMappingInfo getMethodMappingInfo(Method method) {
RequestMappingInfo mappingInfo = null;
if (AnnotationUtils.findAnnotation(method, GetMapping.class) != null) {
GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
mappingInfo = RequestMappingInfo.paths(getMapping.path())
.methods(RequestMethod.GET)
.produces(getMapping.produces())
.consumes(getMapping.consumes())
.headers(getMapping.headers())
.params(getMapping.params())
.build();
} else if (AnnotationUtils.findAnnotation(method, PostMapping.class) != null) {
PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
mappingInfo = RequestMappingInfo.paths(postMapping.path())
.methods(RequestMethod.POST)
.produces(postMapping.produces())
.consumes(postMapping.consumes())
.headers(postMapping.headers())
.params(postMapping.params())
.build();
} else if (AnnotationUtils.findAnnotation(method, PutMapping.class) != null) {
PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
mappingInfo = RequestMappingInfo.paths(putMapping.path())
.methods(RequestMethod.PUT)
.produces(putMapping.produces())
.consumes(putMapping.consumes())
.headers(putMapping.headers())
.params(putMapping.params())
.build();
} else if (AnnotationUtils.findAnnotation(method, DeleteMapping.class) != null) {
DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
mappingInfo = RequestMappingInfo.paths(deleteMapping.path())
.methods(RequestMethod.DELETE)
.produces(deleteMapping.produces())
.consumes(deleteMapping.consumes())
.headers(deleteMapping.headers())
.params(deleteMapping.params())
.build();
}
return mappingInfo;
}
/**
* 判断一个方法是否为处理器方法
*
* @param method
* @return
*/
private boolean isHandlerMethod(Method method) {
return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null ||
AnnotationUtils.findAnnotation(method, GetMapping.class) != null ||
AnnotationUtils.findAnnotation(method, PostMapping.class) != null ||
AnnotationUtils.findAnnotation(method, PutMapping.class) != null ||
AnnotationUtils.findAnnotation(method, DeleteMapping.class) != null;
}
My unloading code is as follows:
public void unloadPlugin(String pluginId, boolean index) {
SpringPlugin plugin = (SpringPlugin) pluginManager.getPlugin(pluginId).getPlugin();
ApplicationContext pluginApplicationContext = plugin.getApplicationContext();
String[] beanDefinitionNames = pluginApplicationContext.getBeanDefinitionNames();
RequestMappingHandlerMapping mappingHandlerMapping = mainApplicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) mainApplicationContext.getAutowireCapableBeanFactory();
for (String beanName : beanDefinitionNames) {
Object bean = pluginApplicationContext.getBean(beanName);
if (mainApplicationContext.containsBean(beanName)){
beanFactory.destroyBean(bean);
beanFactory.destroySingleton(beanName);// 这种方式能清除掉单例缓存
beanFactory.removeBeanDefinition(beanName);
}
if (bean.getClass().isAnnotationPresent(Controller.class) || bean.getClass().isAnnotationPresent(RestController.class)) {
unRegisterMethods(bean, mappingHandlerMapping);
log.info("插件【{}】中的 RestController {} 已从 RequestMappingHandlerMapping 中移除", pluginId, beanName);
}
}
plugin.stop();
closeClassLoader(plugin, pluginId);
pluginManager.deletePlugin(pluginId);
applicationEventPublisher.publishEvent(new PluginLoadedEvent(pluginId));
}
private void closeClassLoader(SpringPlugin plugin, String pluginId) {
Thread.currentThread().setContextClassLoader(pluginManager.getClass().getClassLoader());
ClassLoader pluginClassLoader = plugin.getWrapper().getPluginClassLoader();
if (pluginClassLoader instanceof URLClassLoader) {
try {
((URLClassLoader) pluginClassLoader).close();
} catch (IOException e) {
log.error("插件【{}】的类加载器关闭失败!", pluginId, e);
}
}
}
After using the above code, I can access the interface defined in the bean and controller in the plugin, but when uninstalling, I found that the class loader of the plugin could not be closed correctly, and no error message was output.
I want to know what is the correct way to dynamically load and unload, is there something I have not considered, and I have not found a solution in the official documentation. I really hope you can help me. Thank you very much.
Hello!
I now want to dynamically load and unload plugins so that the main project can access the beans and APIs in the plugins.
My loading code is as follows:
My unloading code is as follows:
After using the above code, I can access the interface defined in the bean and controller in the plugin, but when uninstalling, I found that the class loader of the plugin could not be closed correctly, and no error message was output.
I want to know what is the correct way to dynamically load and unload, is there something I have not considered, and I have not found a solution in the official documentation. I really hope you can help me. Thank you very much.