一种基于Spring Boot实现的支持热插拔的插件化方案

背景

在Spring Boot开发的过程中,可能会遇到一些场景:1)希望在已有的系统添加一块临时代码,用于某项市场验证等。在市场验证结束后又能够将这块临时代码从项目中彻底清除;2)一部分附属于主系统的外围功能,需要独立的开发和维护。此时需要用到插件化的技术来实现。本文将介绍一种在Spring Boot之上实现支持热插拔的插件化方案。

主要思路

通过自定义ClassLoader来加载插件包内的类。并将定义在插件中的Bean注册到Ioc容器中。在此过程中需要打破Java类加载机制本身的双亲委派机制。

双亲委派机制
  1. 当调用一个ClassLoader的loadClass方法时,它先不调用自身的类加载方法,而是将类加载的过程委派给自己的双亲类加载器(调用双亲类加载器的loadClass方法)。
  2. 当双亲类加载器加载成功时,直接返回加载成功的类
  3. 当双亲类加载器加载失败时,再调用自身的findClass方法。
打破双亲委派机制
  1. 在插件化的过程中,在调用ClassLoader的loadClass方法时,需要先判断该类是否属于插件中的类。判断的一个类是否属于插件中的类的方法有很多。比如当一个类的包名匹配**.plugins.{{pluginName}}.{{className}}。就认为这个类来自名称为{{pluginName}}的插件。
  2. 若该类不属于插件中定义的类,则调用系统原来的ClassLoader加载该类,继续按双亲委派机制执行。
  3. 若该类属于插件类,则先根据{{pluginName}}找到相应的插件类加载器,然后调用插件类加载器的loadClass方法。

自定义支持插件的类加载器

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**/
	
}

/**忽略**/
}

注册和注销插件中定义的Api

@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映射方法做进一步处理。

该方案能够实现基础的热插拔插件功能,但是仍然不够全面。例如:

  • 只能识别@Controller、@Service、@Component定义的Bean,对于Spring中继承自@Component注解的Bean以及通过@Bean注解定义的Bean并未处理。
  • 当系统加载了多个插件,且多个插件中有相同的beanName时。即Bean的定义出现冲突时未处理。

欢迎大家在评论区讨论,谢谢阅读。

  • 1
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot插件是指在Spring Boot项目中使用插件机制来实现功能的扩展和灵活性的增强。 首先,Spring Boot提供了许多可扩展的接口和抽象类,如Starter、AutoConfiguration、CommandLineRunner等,通过这些接口和抽象类,我们可以定义自己的插件,并将其集成到Spring Boot应用中。 其次,Spring Boot插件机制可以通过依赖注入和自动配置实现插件的依赖可以通过在pom.xml文件中添加特定的依赖来实现。而自动配置则是通过在META-INF/spring.factories文件中注册相应的自动配置类来实现。当Spring Boot启动时,会根据这些自动配置类来进行相应的自动配置。 此外,Spring Boot支持外部配置,也就是说可以将插件的配置信息以外部的方式进行配置,如application.properties或application.yml文件。这样使得插件的配置更加灵活和可扩展。 最后,Spring Boot插件还可以通过注解、AOP等方式来实现。通过在插件中使用注解或AOP,可以将插件的功能与Spring Boot应用的功能进行解耦,提高代码的可维护性和可测试性。 总之,Spring Boot插件通过提供可扩展的接口和抽象类、依赖注入和自动配置、外部配置以及注解、AOP等方式,使得插件可以轻松地集成到Spring Boot应用中,实现对功能的扩展和灵活的增强。这种插件的机制提高了Spring Boot应用的可扩展性和可维护性,使得开发人员可以更加方便地开发和管理插件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值