赞
踩
在Spring Boot开发的过程中,可能会遇到一些场景:1)希望在已有的系统添加一块临时代码,用于某项市场验证等。在市场验证结束后又能够将这块临时代码从项目中彻底清除;2)一部分附属于主系统的外围功能,需要独立的开发和维护。此时需要用到插件化的技术来实现。本文将介绍一种在Spring Boot之上实现支持热插拔的插件化方案。
通过自定义ClassLoader来加载插件包内的类。并将定义在插件中的Bean注册到Ioc容器中。在此过程中需要打破Java类加载机制本身的双亲委派机制。
HotSwapPluginClassLoader类的loadClass方法会根据加载的类是否属于插件类,以及类对应的插件名来选择合适的插件类加载器,并通过插件类加载器来加载插件类,通过自身的双亲类来加载非插件类。
/** imports **/ public class HotSwapPluginClassLoader extends ClassLoader implements SmartClassLoader { private Map<String, PluginDefinition> pluginDefinitionMap = new ConcurrentHashMap(); public static final HotSwapPluginClassLoader INSTANCE = new HotSwapClassLoader(); @Override protected Class<?> loadClass(String name, boolean resolve) throw ClassNotFoundException { synchronized(this.getClassLoadingLock(name)) { Class<?> c = null; /**获取插件名,若插件名为null表示该类不是插件中定义的类**/ String pluginName = this.getPluginName(name); /**不是插件中的类,按双亲委派机制执行**/ if(pluginName == null) { try { c = this.getParent().loadClass(name); } catch (ClassNotFoundException e) { } } else { PluginDefinition pluginDefinition = this.pluginDefinitionMap.get(pluginName); if(pluginDefinition != null) { try { c = pluginDefinition.getPluginClassLoader().loadClass(name, false); } catch(ClassNotFoundException e) { } } } if(c == null) { throw new ClassNotFoundException(name); } return c; } } /**注册插件**/ public ClassLoader register(String pluginName, File jarFile) { pluginName = Objects.requireNonNull(pluginName); jarFile = Objects.requireNonNull(jarFile); try { URL url = jarFile.toURI().toURL(); ClassLoader pluginClassLoader = new PluginClassLoader(new URL[]{url}, this); this.pluginDefinitionMap.put(pluginName, new PluginDefinition(pluginName, pluginClassLoader)); return pluginClassLoader; } catch(MalformatedURLException e) { throw new RuntimeException(e); } } /**注销插件**/ public void unregister(String pluginName) { pluginName = Objects.requireNonNull(pluginName); PluginClassLoader pluginClassLoader = this.pluginDefinitionMap.get(pluginName).getPluginClassLoader(); try { pluginClassLoader.close(); } catch(IOException e) { e.pringStackTrace(); } this.pluginDefinitionMap.remove(pluginName); } /**根据类名获取插件名,不是插件中定义的类时返回null**/ public String getPluginName(String className) { /**忽略**/ } @Override public boolean isClassReloadable(Class<?> aClass) { /**插件中的定义的类允许重新加载**/ return this.getPluginName(aClass.getName) != null; } public static class PluginClassLoader extends URLClassLoader implements SmartClassLoader { private final HotSwapPluginClassLoader parent; protected PluginClassLoader(URL[] urls, HotSwapPluginClassLoader parent) { super(urls, parent); this.parent = parent; } @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = null; if(this.parent.getPluginName(name) != null) { /**插件类,自己处理**/ c = this.findLoadedClass(name); if(c == null) { try { c = this.findClass(name); } catch(ClassNotFoundException e) { } } } else { /**非插件类,委托双亲处理**/ c = this.parent.loadClass(name); } if(c != null) { if(resolve) resolveClass(c); return c; } throw new ClassNotFoundException(name); } @Override public boolean isClassReloadable(Class<?> aClass) { return true; } } }
有了插件类加载器HotSwapPluginClassLoader后,就可以调用该类的register方法来注册插件的jar文件,并加载插件中的类了。但是还需要一个服务来将插件的装载、卸载等过程转换为一般的api操作。
先定义插件信息Holder类,来保存插件中的Bean信息
public class PluginHolder {
private String pluginName;
/**api请求信息**/
private Set<RequestMappingInfo> requestMappingInfoSet = new HashSet();
/**插件中的bean定义信息**/
private Set<BeanDefinitionWrap> beanDefinitionWrapSet = new HashSet();
/** getters and setters **/
}
装载插件loadPlugin和卸载插件unloadPlugin
@Service public class PluginService { /**忽略**/ private ConcurrentHashMap<String, PluginHolder> pluginHolderMap = new ConcurrentHashMap(); /**装载插件**/ public synchronized void loadPlugin(String pluginName, File jarFile) { HotSwapPluginClassLoader classLoader = HotSwapPluginClassLoader.INSTANCE; DefaultListableBeanFactory registry = (DefaultListableBeanFactory) this.applicationContext.getAutowiredCapableBeanFactory(); PluginHolder holder = this.pluginHolderMap.get(pluginName); if(holder != null) { this.unloadPlugin(holder.getPluginName()); } holder = new PluginHolder(); holder.setPluginName(pluginName); this.pluginHolderMap.put(pluginName, holder); ClassLoader pluginClassLoader = classLoader.register(pluginName, jarFile); /**设置动态代理的ClassLoader**/ this.setProxyClassLoader(registry, pluginClassLoader); try { /**获取插件中的Bean定义,并注册Bean**/ Set<BeanDefinitionWrap> beanDefinitionWraps = this.doGetBeanDefinitions(pluginClassLoader); for(BeanDefinitionWrap wrap : beanDefinitionWraps) { try { if(registry.containsSingleton(wrap.getBeanName())) { registry.destroySingleton(wrap.getBeanName()); } } catch(Exception e) { } try{ if(registry.containsBeanDefinition(wrap.getBeanName())) { register.removeBeanDefinition(wrap.getBeanName()); } } catch(Exception e) { } registry.registerBeanDefinition(wrap.getBeanName(), wrap.getDefinition()); holder.getBeanDefinitionWrapSet().add(wrap); } /**注册插件中定义的Controller**/ for(BeanDefinitionWrap wrap : beanDefinitionWraps) { Set<RequestMappingInfo> requestMappingInfoSet = this.doRegisterRequestController(wrap.getBeanName(), wrap.getBeanType()); holder.getRequestMappingInfoSet().addAll(requestMappingInfoSet); } } catch(Exception e) { this.doReleasePluginHolder(holder); } finally { /**还原动态代理的ClassLoader**/ this.setProxyClassLoader(registry, classLoader); } } /**卸载插件**/ public void unloadPlugin(pluginName) { PluginHolder holder = this.pluginHolderMap.get(pluginName); HotSwapPluginClassLoader classLoader = HotSwapPluginClassLoader.INSTANCE; if(holder != null) { /**释放插件**/ this.doReleasePluginHolder(holder); /**注销插件的类加载器**/ classLoader.unregister(pluginName); } } /**设置动态代理的ClassLoader,对于通过CGLib实现的动态代理类,加载过程中不是使用当前线程的ClassLoader,而是使用AbstractAutoProxyCreator中的proxyClassLoader。因此,需要手动修改AbstractAutoProxyCreator中的proxyClassLoader属性。**/ private void setProxyClassLoader(DefaultListableBeanFactory registry, ClassLoader classLoader) { /**AbstractAutoProxyCreator类是BeanPostProcessor接口的实现,通过遍历BeaenPostProcessor列表获取到它的实例**/ List<BeanPostProcessor> postProcessors = registry.getBeanPostProcessors(); for(BeanPostProcessor postProcessor : postProcessors) { if(postProcessor instanceof AbstractAutoProxyCreator) { ((AbstractAutoProxyCreator) postProcessor).setProxyClassLoader(classLoader); } } } /**获取插件中的Bean定义**/ private Set<BeanDefinitionWrap> doGetBeanDefinitions(ClassLoader pluginClassLoader) { /**Bean的候选集合**/ Set<BeanDefinitionWrap> candidates = new HashSet(); ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(pluginClassLoader); Resource[] resources = resourcePatternResolver.getResources("classpath*:**/plugins/**/*.**"); AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); DefaultListableBeanFactory register = (DefaultListableBeanFactory) this.applicationContext.getAutowireCapableBeanFactory(); MetadataReaderFactory metadata = new SimpleMetadataReaderFactory(); for(Resource resource : resources) { try { MetadataReader metadataReader = metadata.getMetadataReader(resource); String className = metadataReader.getClassMetadata().getClassName(); Class beanClass = pluginClassLoader.loadClass(className); AnnotatedGenericBeanDefinition abd = new PluginClassBeanDefinition(beanClass); BeanDefinitionWrap wrap = new BeanDefinitionWrap(abd); wrap.setBeanType(beanClass); candicates.add(wrap); } catch(Throwable e) { e.printStackTrace(); } } /**筛选出bean**/ Set<BeanDefinitionWrap> result = new HashSet(); for(BeanDefinitionWrap wrap : candicates) { Class<?> aClass = wrap.getBeanType(); Controller controller = aClass.getAnnotation(Controller.class); RestController restController = aClass.getAnnotation(RestController.class); Service service = aClass.getAnnotation(Service.class); Component component = aClass.getAnnotation(Component.class); wrap.setController(controller != null || restController != null); wrap.setService(service != null); wrap.setComponent(component != null); if(wrap.isComponent() || wrap.isController() || wrap.isService()) { wrap.setBeanName(beanNameGenerator.generateBeanName(wrap.getDefinition(), register)); result.add(wrap); } } return result; } private void doReleasePluginHolder(PluginHolder holder) { /**注销插件中定义的Api请求**/ for(RequestMappingInfo requestMappingInfo : holder.getRequestMappingInfoSet()) { try { this.doUnregisterRequestMappingInfo(requestMappingInfo); } catch(Exception e) { } } /**注销插件中定义的Bean**/ DefaultListableBeanFactory registry = (DefaultListableBeanFactory) this.applicationContext.getAutowiredBeanFactory(); for(BeanDefinitionWrap wrap : holder.getBeanDefinitionWrapSet()) { try { registry.removeBeanDefinition(beanDefinitionWrap.getBeanName()); } catch(Exception e) { } } } /**Bean定义修饰,包含附加信息**/ private static class BeanDefinitionWrap { /**Bean定义**/ BeanDefinition definition; /**被@Controller注解**/ private boolean isController; /**被@Service注解**/ private boolean isService; /**被@Component注解**/ private boolean isComponent; private String beanName; private Class<?> beanType; public BeanDefinitionWrap(BeanDefinition definition) { this.definition = definition; } /** getters and setters**/ } /**忽略**/ }
@Service public class PluginService { /**忽略**/ /**注册插件中定义的api**/ private Set<RequestMappingInfo> doRegisterRequestController(String beanName, Class controllerClzz) { Object proxy = this.applicationContext.getBean(beanName); RequestMappingHandlerMapping requestMappingHandlerMapping = this.applicationContext.getBean(RequestMappingHandlerMapping.class); /**通过反射的方式调用getMappingForMethod方法**/ Method getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class); getMappingForMethod.setAccessible(true); Set<RequestMappingInfo> requestMappingInfoSet = new HashSet(); try { Method[] methodArr = controllerClzz.getMethods(); for(Method method : methodArr) { RequestMappingInfo mappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, controllerClzz); if(mappingInfo != null) { requestMappingHandlerMapping.registerMapping(mappingInfo, proxy, method); requestMappingInfoSet.add(mappingInfo); } } } catch(Exception e) { e.pringStackTrace(); } return requestMappingInfoSet; } /**注销插件中定义的api**/ private void doUnregisterRequestMappingInfo(RequestMappingInfo requestMappingInfo) { RequestMappingHandlerMapping requestMappingHandlerMapping = this.applicationContext.getBean(RequestMappingHandlerMapping.class); requestMappingHandlerMapping.unregisterMapping(requestMappingInfo); } /**忽略**/ }
本文所阐述的支持热替换的插件化方案,主要思路是通过自定义类加载器来加载插件中的类,并从中筛选出有Bean定义的类,并将Bean定义注册到系统Ioc容器中。若Bean中存在@Controller注解的类,还需要对该类中的api映射方法做进一步处理。
该方案能够实现基础的热插拔插件功能,但是仍然不够全面。例如:
欢迎大家在评论区讨论,谢谢阅读。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。