之前已经写了一篇关于《几种Java热插拔技术实现总结》,在该文中我总结了好几种Java实现热插拔的技术,其中各有优缺点,在这篇文章我将介绍Java热插拔技术在我司项目中的实践。
前言
在开始之前,先看下插件系统的整体框架
- 插件开发模拟环境 “插件开发模拟环境”主要用于插件的开发和测试,一个独立项目,提供给插件开发人员使用。开发模拟环境依赖插件核心包、插件依赖的主程序包。 插件核心包-负责插件的加载,安装、注册、卸载 插件依赖的主程序包-提供插件开发测试的主程序依赖
- 主程序 插件的正式安装使用环境,线上环境。插件在本地开发测试完成后,通过插件管理页面安装到线上环境进行插件验证。可以分多个环境,线上dev环境提供插件的线上验证,待验证完成后,再发布到prod环境。
代码实现
插件加载流程
在监听到Spring Boot启动后,插件开始加载,从配置文件中获取插件配置、创建插件监听器(用于主程序监听插件启动、停止事件,根据事件自定逻辑)、根据获取的插件配置从指定目录加载插件配置信息(插件id、插件版本、插件描述、插件所在路径、插件启动状态(后期更新))、配置信息加载完成后将插件class类注册到Spring返回插件上下文、最后启动完成。
插件核心包
基础常量和类
PluginConstants
插件常量
publicclassPluginConstants{publicstaticfinalString TARGET ="target";publicstaticfinalString POM ="pom.xml";publicstaticfinalString JAR_SUFFIX =".jar";publicstaticfinalString REPACKAGE ="repackage";publicstaticfinalString CLASSES ="classes";publicstaticfinalString CLASS_SUFFIX =".class";publicstaticfinalString MANIFEST ="MANIFEST.MF";publicstaticfinalString PLUGINID ="pluginId";publicstaticfinalString PLUGINVERSION ="pluginVersion";publicstaticfinalString PLUGINDESCRIPTION ="pluginDescription";}
PluginState
插件状态
@AllArgsConstructorpublicenumPluginState{/**
* 被禁用状态
*/DISABLED("DISABLED"),/**
* 启动状态
*/STARTED("STARTED"),/**
* 停止状态
*/STOPPED("STOPPED");privatefinalString status;}
RuntimeMode
插件运行环境
@Getter@AllArgsConstructorpublicenumRuntimeMode{/**
* 开发环境
*/DEV("dev"),/**
* 生产环境
*/PROD("prod");privatefinalString mode;publicstaticRuntimeModebyName(String model){if(DEV.name().equalsIgnoreCase(model)){returnRuntimeMode.DEV;}else{returnRuntimeMode.PROD;}}}
PluginInfo
插件基本信息,重写了hashcode和equals,根据插件id进行去重
@Data@BuilderpublicclassPluginInfo{/**
* 插件id
*/privateString id;/**
* 版本
*/privateString version;/**
* 描述
*/privateString description;/**
* 插件路径
*/privateString path;/**
* 插件启动状态
*/privatePluginState pluginState;@Overridepublicbooleanequals(Object obj){if(this== obj)returntrue;if(obj ==null)returnfalse;if(getClass()!= obj.getClass())returnfalse;PluginInfo other =(PluginInfo) obj;returnObjects.equals(id, other.id);}@OverridepublicinthashCode(){returnObjects.hash(id);}publicvoidsetPluginState(PluginState started){this.pluginState = started;}}
插件监听器
PluginListener
插件监听器接口
publicinterfacePluginListener{/**
* 注册插件成功
* @param pluginInfo 插件信息
*/defaultvoidstartSuccess(PluginInfo pluginInfo){}/**
* 启动失败
* @param pluginInfo 插件信息
* @param throwable 异常信息
*/defaultvoidstartFailure(PluginInfo pluginInfo,Throwable throwable){}/**
* 卸载插件成功
* @param pluginInfo 插件信息
*/defaultvoidstopSuccess(PluginInfo pluginInfo){}/**
* 停止失败
* @param pluginInfo 插件信息
* @param throwable 异常信息
*/defaultvoidstopFailure(PluginInfo pluginInfo,Throwable throwable){}}
DefaultPluginListenerFactory
插件监听工厂,对自定义插件监听器发送事件
publicclassDefaultPluginListenerFactoryimplementsPluginListener{privatefinalList<PluginListener> listeners;publicDefaultPluginListenerFactory(ApplicationContext applicationContext){
listeners =newArrayList<>();addExtendPluginListener(applicationContext);}publicDefaultPluginListenerFactory(){
listeners =newArrayList<>();}privatevoidaddExtendPluginListener(ApplicationContext applicationContext){Map<String,PluginListener> beansOfTypeMap = applicationContext.getBeansOfType(PluginListener.class);if(!beansOfTypeMap.isEmpty()){
listeners.addAll(beansOfTypeMap.values());}}publicsynchronizedvoidaddPluginListener(PluginListener pluginListener){if(pluginListener !=null){
listeners.add(pluginListener);}}publicList<PluginListener>getListeners(){return listeners;}@OverridepublicvoidstartSuccess(PluginInfo pluginInfo){for(PluginListener listener : listeners){try{
listener.startSuccess(pluginInfo);}catch(Exception e){}}}@OverridepublicvoidstartFailure(PluginInfo pluginInfo,Throwable throwable){for(PluginListener listener : listeners){try{
listener.startFailure(pluginInfo, throwable);}catch(Exception e){}}}@OverridepublicvoidstopSuccess(PluginInfo pluginInfo){for(PluginListener listener : listeners){try{
listener.stopSuccess(pluginInfo);}catch(Exception e){}}}@OverridepublicvoidstopFailure(PluginInfo pluginInfo,Throwable throwable){for(PluginListener listener : listeners){try{
listener.stopFailure(pluginInfo, throwable);}catch(Exception e){}}}}
工具类
DeployUtils
部署工具类,读取jar包中的文件,判断class是否为Spring bean等
@Slf4jpublicclassDeployUtils{/**
* 读取jar包中所有类文件
*/publicstaticSet<String>readJarFile(String jarAddress){Set<String> classNameSet =newHashSet<>();try(JarFile jarFile =newJarFile(jarAddress)){Enumeration<JarEntry> entries = jarFile.entries();//遍历整个jar文件while(entries.hasMoreElements()){JarEntry jarEntry = entries.nextElement();String name = jarEntry.getName();if(name.endsWith(PluginConstants.CLASS_SUFFIX)){String className = name.replace(PluginConstants.CLASS_SUFFIX,"").replaceAll("/",".");
classNameSet.add(className);}}}catch(Exception e){
log.warn("加载jar包失败", e);}return classNameSet;}publicstaticInputStreamreadManifestJarFile(File jarAddress){try{JarFile jarFile =newJarFile(jarAddress);//遍历整个jar文件Enumeration<JarEntry> entries = jarFile.entries();while(entries.hasMoreElements()){JarEntry jarEntry = entries.nextElement();String name = jarEntry.getName();if(name.contains(PluginConstants.MANIFEST)){return jarFile.getInputStream(jarEntry);}}}catch(Exception e){
log.warn("加载jar包失败", e);}returnnull;}/**
* 方法描述 判断class对象是否带有spring的注解
*/publicstaticbooleanisSpringBeanClass(Class<?> cls){if(cls ==null){returnfalse;}//是否是接口if(cls.isInterface()){returnfalse;}//是否是抽象类if(Modifier.isAbstract(cls.getModifiers())){returnfalse;}if(cls.getAnnotation(Component.class)!=null){returntrue;}if(cls.getAnnotation(Mapper.class)!=null){returntrue;}if(cls.getAnnotation(Service.class)!=null){returntrue;}if(cls.getAnnotation(RestController.class)!=null){returntrue;}returnfalse;}publicstaticbooleanisController(Class<?> cls){if(cls.getAnnotation(Controller.class)!=null){returntrue;}if(cls.getAnnotation(RestController.class)!=null){returntrue;}returnfalse;}publicstaticbooleanisHaveRequestMapping(Method method){returnAnnotationUtils.findAnnotation(method,RequestMapping.class)!=null;}/**
* 类名首字母小写 作为spring容器beanMap的key
*/publicstaticStringtransformName(String className){String tmpstr = className.substring(className.lastIndexOf(".")+1);return tmpstr.substring(0,1).toLowerCase()+ tmpstr.substring(1);}/**
* 读取class文件
* @param path
* @return
*/publicstaticSet<String>readClassFile(String path){if(path.endsWith(PluginConstants.JAR_SUFFIX)){returnreadJarFile(path);}else{List<File> pomFiles =FileUtil.loopFiles(path, file -> file.getName().endsWith(PluginConstants.CLASS_SUFFIX));Set<String> classNameSet =newHashSet<>();for(File file : pomFiles){String className =CharSequenceUtil.subBetween(file.getPath(),PluginConstants.CLASSES +File.separator,PluginConstants.CLASS_SUFFIX).replace(File.separator,".");
classNameSet.add(className);}return classNameSet;}}}
插件自动化配置
PluginAutoConfiguration
插件自动化配置信息
@ConfigurationProperties(prefix ="plugin")@DatapublicclassPluginAutoConfiguration{/**
* 是否启用插件功能
*/@Value("${enable:true}")privateBoolean enable;/**
* 运行模式
* 开发环境: development、dev
* 生产/部署 环境: deployment、prod
*/@Value("${runMode:dev}")privateString runMode;/**
* 插件的路径
*/privateList<String> pluginPath;/**
* 在卸载插件后, 备份插件的目录
*/@Value("${backupPath:backupPlugin}")privateString backupPath;publicRuntimeModeenvironment(){returnRuntimeMode.byName(runMode);}}
PluginStarter
插件自动化配置,配置在spring.factories中
@Configuration(proxyBeanMethods =true)@EnableConfigurationProperties(PluginAutoConfiguration.class)@Import(DefaultPluginApplication.class)publicclassPluginStarter{}
PluginConfiguration
配置插件管理操作类,主程序可以注入该类,操作插件的安装、卸载、获取插件上下文
@ConfigurationpublicclassPluginConfiguration{@BeanpublicPluginManagercreatePluginManager(PluginAutoConfiguration configuration,ApplicationContext applicationContext){returnnewDefaultPluginManager(configuration, applicationContext);}}
插件加载注册
DefaultPluginApplication
监听Spring Boot启动完成,加载插件,调用父类的加载方法,获取主程序上下文
importorg.springframework.beans.BeansException;importorg.springframework.boot.context.event.ApplicationStartedEvent;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.context.ApplicationListener;importorg.springframework.context.annotation.Import;@Import(PluginConfiguration.class)publicclassDefaultPluginApplicationextendsAbstractPluginApplicationimplementsApplicationContextAware,ApplicationListener<ApplicationStartedEvent>{privateApplicationContext applicationContext;//主程序启动后加载插件@OverridepublicvoidonApplicationEvent(ApplicationStartedEvent event){super.initialize(applicationContext);}@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{this.applicationContext = applicationContext;}}
AbstractPluginApplication
提供插件的加载,从主程序中获取插件配置,获取插件管理操作类
importjava.util.Objects;importjava.util.concurrent.atomic.AtomicBoolean;importorg.springframework.beans.factory.BeanCreationException;importorg.springframework.context.ApplicationContext;importlombok.extern.slf4j.Slf4j;@Slf4jpublicabstractclassAbstractPluginApplication{privatefinalAtomicBoolean beInitialized =newAtomicBoolean(false);publicsynchronizedvoidinitialize(ApplicationContext applicationContext){Objects.requireNonNull(applicationContext,"ApplicationContext can't be null");if(beInitialized.get()){thrownewRuntimeException("Plugin has been initialized");}//获取配置PluginAutoConfiguration configuration =getConfiguration(applicationContext);if(Boolean.FALSE.equals(configuration.getEnable())){
log.info("插件已禁用");return;}try{
log.info("插件加载环境: {},插件目录: {}", configuration.getRunMode(),String.join(",", configuration.getPluginPath()));DefaultPluginManager pluginManager =getPluginManager(applicationContext);
pluginManager.createPluginListenerFactory();
pluginManager.loadPlugins();
beInitialized.set(true);
log.info("插件启动完成");}catch(Exception e){
log.error("初始化插件异常", e);}}protectedPluginAutoConfigurationgetConfiguration(ApplicationContext applicationContext){PluginAutoConfiguration configuration =null;try{
configuration = applicationContext.getBean(PluginAutoConfiguration.class);}catch(Exception e){// no show exception}if(configuration ==null){thrownewBeanCreationException("没有发现 <PluginAutoConfiguration> Bean");}return configuration;}protectedDefaultPluginManagergetPluginManager(ApplicationContext applicationContext){DefaultPluginManager pluginManager =null;try{
pluginManager = applicationContext.getBean(DefaultPluginManager.class);}catch(Exception e){// no show exception}if(pluginManager ==null){thrownewBeanCreationException("没有发现 <DefaultPluginManager> Bean");}return pluginManager;}}
DefaultPluginManager
插件操作类,管理插件的加载、安装、卸载,主程序使用该类对插件进行操作
importjava.io.File;importjava.io.FileInputStream;importjava.io.IOException;importjava.io.InputStream;importjava.lang.annotation.Annotation;importjava.nio.file.Path;importjava.nio.file.Paths;importjava.util.ArrayList;importjava.util.HashSet;importjava.util.List;importjava.util.Map;importjava.util.Set;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.atomic.AtomicBoolean;importjava.util.jar.Attributes;importjava.util.jar.Manifest;importorg.apache.maven.model.Model;importorg.apache.maven.model.io.xpp3.MavenXpp3Reader;importorg.codehaus.plexus.util.xml.pull.XmlPullParserException;importorg.springframework.context.ApplicationContext;importcom.greentown.plugin.constants.PluginConstants;importcom.greentown.plugin.constants.PluginState;importcom.greentown.plugin.constants.RuntimeMode;importcom.greentown.plugin.listener.DefaultPluginListenerFactory;importcom.greentown.plugin.util.DeployUtils;importcn.hutool.core.collection.CollUtil;importcn.hutool.core.date.DateUtil;importcn.hutool.core.io.FileUtil;importcn.hutool.core.io.file.PathUtil;importcn.hutool.core.text.CharSequenceUtil;importlombok.extern.slf4j.Slf4j;@Slf4jpublicclassDefaultPluginManagerimplementsPluginManager{privatePluginAutoConfiguration pluginAutoConfiguration;privateApplicationContext applicationContext;privateDefaultPluginListenerFactory pluginListenerFactory;privatePluginClassRegister pluginClassRegister;privateMap<String,ApplicationContext> pluginBeans =newConcurrentHashMap<>();privateMap<String,PluginInfo> pluginInfoMap =newConcurrentHashMap<>();privatefinalAtomicBoolean loaded =newAtomicBoolean(false);publicDefaultPluginManager(PluginAutoConfiguration pluginAutoConfiguration,ApplicationContext applicationContext){this.pluginAutoConfiguration = pluginAutoConfiguration;this.applicationContext = applicationContext;this.pluginClassRegister =newPluginClassRegister(applicationContext, pluginAutoConfiguration, pluginBeans);}publicvoidcreatePluginListenerFactory(){this.pluginListenerFactory =newDefaultPluginListenerFactory(applicationContext);}@OverridepublicList<PluginInfo>loadPlugins()throwsException{if(loaded.get()){thrownewPluginException("不能重复调用: loadPlugins");}//从配置路径获取插件目录//解析插件jar包中的配置,生成配置对象List<PluginInfo> pluginInfoList =loadPluginsFromPath(pluginAutoConfiguration.getPluginPath());if(CollUtil.isEmpty(pluginInfoList)){
log.warn("路径下未发现任何插件");return pluginInfoList;}//注册插件for(PluginInfo pluginInfo : pluginInfoList){start(pluginInfo);}
loaded.set(true);return pluginInfoList;}privateList<PluginInfo>loadPluginsFromPath(List<String> pluginPath)throwsIOException,XmlPullParserException{List<PluginInfo> pluginInfoList =newArrayList<>();for(String path : pluginPath){Path resolvePath =Paths.get(path);Set<PluginInfo> pluginInfos =buildPluginInfo(resolvePath);
pluginInfoList.addAll(pluginInfos);}return pluginInfoList;}privateSet<PluginInfo>buildPluginInfo(Path path)throwsIOException,XmlPullParserException{Set<PluginInfo> pluginInfoList =newHashSet<>();//开发环境if(RuntimeMode.DEV == pluginAutoConfiguration.environment()){List<File> pomFiles =FileUtil.loopFiles(path.toString(), file ->PluginConstants.POM.equals(file.getName()));for(File file : pomFiles){MavenXpp3Reader reader =newMavenXpp3Reader();Model model = reader.read(newFileInputStream(file));PluginInfo pluginInfo =PluginInfo.builder().id(model.getArtifactId()).version(model.getVersion()==null? model.getParent().getVersion(): model.getVersion()).description(model.getDescription()).build();//开发环境重新定义插件路径,需要指定到classes目录
pluginInfo.setPath(CharSequenceUtil.subBefore(path.toString(), pluginInfo.getId(),false)+File.separator + pluginInfo.getId()+File.separator +PluginConstants.TARGET
+File.separator +PluginConstants.CLASSES);
pluginInfoList.add(pluginInfo);}}//生产环境从jar包中读取if(RuntimeMode.PROD == pluginAutoConfiguration.environment()){//获取jar包列表List<File> jarFiles =FileUtil.loopFiles(path.toString(), file -> file.getName().endsWith(PluginConstants.REPACKAGE +PluginConstants.JAR_SUFFIX));for(File jarFile : jarFiles){//读取配置try(InputStream jarFileInputStream =DeployUtils.readManifestJarFile(jarFile)){Manifest manifest =newManifest(jarFileInputStream);Attributes attr = manifest.getMainAttributes();PluginInfo pluginInfo =PluginInfo.builder().id(attr.getValue(PluginConstants.PLUGINID)).version(attr.getValue(PluginConstants.PLUGINVERSION)).description(attr.getValue(PluginConstants.PLUGINDESCRIPTION)).path(jarFile.getPath()).build();
pluginInfoList.add(pluginInfo);}catch(Exception e){
log.warn("插件{}配置读取异常", jarFile.getName());}}}return pluginInfoList;}@OverridepublicPluginInfoinstall(Path pluginPath){if(RuntimeMode.PROD != pluginAutoConfiguration.environment()){thrownewPluginException("插件安装只适用于生产环境");}try{Set<PluginInfo> pluginInfos =buildPluginInfo(pluginPath);if(CollUtil.isEmpty(pluginInfos)){thrownewPluginException("插件不存在");}PluginInfo pluginInfo =(PluginInfo) pluginInfos.toArray()[0];if(pluginInfoMap.get(pluginInfo.getId())!=null){
log.info("已存在同类插件{},将覆盖安装", pluginInfo.getId());}uninstall(pluginInfo.getId());start(pluginInfo);return pluginInfo;}catch(Exception e){thrownewPluginException("插件安装失败", e);}}privatevoidstart(PluginInfo pluginInfo){try{
pluginClassRegister.register(pluginInfo);
pluginInfo.setPluginState(PluginState.STARTED);
pluginInfoMap.put(pluginInfo.getId(), pluginInfo);
log.info("插件{}启动成功", pluginInfo.getId());
pluginListenerFactory.startSuccess(pluginInfo);}catch(Exception e){
log.error("插件{}注册异常", pluginInfo.getId(), e);
pluginListenerFactory.startFailure(pluginInfo, e);}}@Overridepublicvoiduninstall(String pluginId){if(RuntimeMode.PROD != pluginAutoConfiguration.environment()){thrownewPluginException("插件卸载只适用于生产环境");}PluginInfo pluginInfo = pluginInfoMap.get(pluginId);if(pluginInfo ==null){return;}stop(pluginInfo);backupPlugin(pluginInfo);clear(pluginInfo);}@OverridepublicPluginInfostart(String pluginId){PluginInfo pluginInfo = pluginInfoMap.get(pluginId);start(pluginInfo);return pluginInfo;}@OverridepublicPluginInfostop(String pluginId){PluginInfo pluginInfo = pluginInfoMap.get(pluginId);stop(pluginInfo);return pluginInfo;}privatevoidclear(PluginInfo pluginInfo){PathUtil.del(Paths.get(pluginInfo.getPath()));
pluginInfoMap.remove(pluginInfo.getId());}privatevoidstop(PluginInfo pluginInfo){try{
pluginClassRegister.unRegister(pluginInfo);
pluginInfo.setPluginState(PluginState.STOPPED);
pluginListenerFactory.stopSuccess(pluginInfo);
log.info("插件{}停止成功", pluginInfo.getId());}catch(Exception e){
log.error("插件{}停止异常", pluginInfo.getId(), e);}}privatevoidbackupPlugin(PluginInfo pluginInfo){String backupPath = pluginAutoConfiguration.getBackupPath();if(CharSequenceUtil.isBlank(backupPath)){return;}String newName = pluginInfo.getId()+DateUtil.now()+PluginConstants.JAR_SUFFIX;String newPath = backupPath +File.separator + newName;FileUtil.copyFile(pluginInfo.getPath(), newPath);}@OverridepublicApplicationContextgetApplicationContext(String pluginId){return pluginBeans.get(pluginId);}@OverridepublicList<Object>getBeansWithAnnotation(String pluginId,Class<?extendsAnnotation> annotationType){ApplicationContext pluginApplicationContext = pluginBeans.get(pluginId);if(pluginApplicationContext !=null){Map<String,Object> beanMap = pluginApplicationContext.getBeansWithAnnotation(annotationType);returnnewArrayList<>(beanMap.values());}returnnewArrayList<>(0);}}
PluginClassRegister
插件动态注册、动态卸载,解析插件class,判断是否为Spring Bean或Spring 接口,是注册到Spring 中
publicclassPluginClassRegister{privateApplicationContext applicationContext;privateRequestMappingHandlerMapping requestMappingHandlerMapping;privateMethod getMappingForMethod;privatePluginAutoConfiguration configuration;privateMap<String,ApplicationContext> pluginBeans;privateMap<String,Set<RequestMappingInfo>> requestMappings =newConcurrentHashMap<>();publicPluginClassRegister(ApplicationContext applicationContext,PluginAutoConfiguration configuration,Map<String,ApplicationContext> pluginBeans){this.applicationContext = applicationContext;this.requestMappingHandlerMapping =getRequestMapping();this.getMappingForMethod =getRequestMethod();this.configuration = configuration;this.pluginBeans = pluginBeans;}publicApplicationContextregister(PluginInfo pluginInfo){ApplicationContext pluginApplicationContext =registerBean(pluginInfo);
pluginBeans.put(pluginInfo.getId(), pluginApplicationContext);return pluginApplicationContext;}publicbooleanunRegister(PluginInfo pluginInfo){returnunRegisterBean(pluginInfo);}privatebooleanunRegisterBean(PluginInfo pluginInfo){GenericWebApplicationContext pluginApplicationContext =(GenericWebApplicationContext) pluginBeans.get(pluginInfo.getId());
pluginApplicationContext.close();//取消注册controllerSet<RequestMappingInfo> requestMappingInfoSet = requestMappings.get(pluginInfo.getId());if(requestMappingInfoSet !=null){
requestMappingInfoSet.forEach(this::unRegisterController);}
requestMappings.remove(pluginInfo.getId());
pluginBeans.remove(pluginInfo.getId());returntrue;}privatevoidunRegisterController(RequestMappingInfo requestMappingInfo){
requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);}privateApplicationContextregisterBean(PluginInfo pluginInfo){String path = pluginInfo.getPath();Set<String> classNames =DeployUtils.readClassFile(path);URLClassLoader classLoader =null;try{//class 加载器URL jarURL =newFile(path).toURI().toURL();
classLoader =newURLClassLoader(new URL[]{ jarURL },Thread.currentThread().getContextClassLoader());//一个插件创建一个applicationContextGenericWebApplicationContext pluginApplicationContext =newGenericWebApplicationContext();
pluginApplicationContext.setResourceLoader(newDefaultResourceLoader(classLoader));//注册beanList<String> beanNames =newArrayList<>();for(String className : classNames){Class clazz = classLoader.loadClass(className);if(DeployUtils.isSpringBeanClass(clazz)){String simpleClassName =DeployUtils.transformName(className);BeanDefinitionRegistry beanDefinitonRegistry =(BeanDefinitionRegistry) pluginApplicationContext.getBeanFactory();BeanDefinitionBuilder usersBeanDefinitionBuilder =BeanDefinitionBuilder.genericBeanDefinition(clazz);
usersBeanDefinitionBuilder.setScope("singleton");
beanDefinitonRegistry.registerBeanDefinition(simpleClassName, usersBeanDefinitionBuilder.getRawBeanDefinition());
beanNames.add(simpleClassName);}}//刷新上下文
pluginApplicationContext.refresh();//注入bean和注册接口Set<RequestMappingInfo> pluginRequestMappings =newHashSet<>();for(String beanName : beanNames){//注入beanObject bean = pluginApplicationContext.getBean(beanName);injectService(bean);//注册接口Set<RequestMappingInfo> requestMappingInfos =registerController(bean);
requestMappingInfos.forEach(requestMappingInfo ->{
log.info("插件{}注册接口{}", pluginInfo.getId(), requestMappingInfo);});
pluginRequestMappings.addAll(requestMappingInfos);}
requestMappings.put(pluginInfo.getId(), pluginRequestMappings);return pluginApplicationContext;}catch(Exception e){thrownewPluginException("注册bean异常", e);}finally{try{if(classLoader !=null){
classLoader.close();}}catch(IOException e){
log.error("classLoader关闭失败", e);}}}privateSet<RequestMappingInfo>registerController(Object bean){Class<?> aClass = bean.getClass();Set<RequestMappingInfo> requestMappingInfos =newHashSet<>();if(Boolean.TRUE.equals(DeployUtils.isController(aClass))){Method[] methods = aClass.getDeclaredMethods();for(Method method : methods){if(DeployUtils.isHaveRequestMapping(method)){try{RequestMappingInfo requestMappingInfo =(RequestMappingInfo)
getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
requestMappingHandlerMapping.registerMapping(requestMappingInfo, bean, method);
requestMappingInfos.add(requestMappingInfo);}catch(Exception e){
log.error("接口注册异常", e);}}}}return requestMappingInfos;}privatevoidinjectService(Object instance){if(instance==null){return;}Field[] fields =ReflectUtil.getFields(instance.getClass());//instance.getClass().getDeclaredFields();for(Field field : fields){if(Modifier.isStatic(field.getModifiers())){continue;}Object fieldBean =null;// with bean-id, bean could be found by both @Resource and @Autowired, or bean could only be found by @Autowiredif(AnnotationUtils.getAnnotation(field,Resource.class)!=null){try{Resource resource =AnnotationUtils.getAnnotation(field,Resource.class);if(resource.name()!=null&& resource.name().length()>0){
fieldBean = applicationContext.getBean(resource.name());}else{
fieldBean = applicationContext.getBean(field.getName());}}catch(Exception e){}if(fieldBean==null){
fieldBean = applicationContext.getBean(field.getType());}}elseif(AnnotationUtils.getAnnotation(field,Autowired.class)!=null){Qualifier qualifier =AnnotationUtils.getAnnotation(field,Qualifier.class);if(qualifier!=null&& qualifier.value()!=null&& qualifier.value().length()>0){
fieldBean = applicationContext.getBean(qualifier.value());}else{
fieldBean = applicationContext.getBean(field.getType());}}if(fieldBean!=null){
field.setAccessible(true);try{
field.set(instance, fieldBean);}catch(IllegalArgumentException e){
log.error(e.getMessage(), e);}catch(IllegalAccessException e){
log.error(e.getMessage(), e);}}}}privateMethodgetRequestMethod(){try{Method method =ReflectUtils.findDeclaredMethod(requestMappingHandlerMapping.getClass(),"getMappingForMethod",newClass[]{Method.class,Class.class});
method.setAccessible(true);return method;}catch(Exception ex){
log.error("反射获取detectHandlerMethods异常", ex);}returnnull;}privateRequestMappingHandlerMappinggetRequestMapping(){return(RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");}}
插件Mock包
plugin-mock
提供插件的开发模拟测试相关的依赖,以Jar包方式提供,根据具体项目提供依赖
插件开发环境
一个独立的项目,依赖上述提供的插件核心包、插件Mock包,提供给插件开发人员使用。
main-application:插件开发测试的主程序
plugins:插件开发目录
总结
在最开始的使用,我们的插件使用Spring Brick来开发,光在集成过程中就发现不少问题,特别是依赖冲突很多,并且对插件的加载比较慢,导致主程序启动慢。
在自研插件后,该插件加载启动使用动态注入Spring的方式,相比较Spring Brick的插件独立Spring Boot方式加载速度更快,占用内存更小,虽然还不支持Freemark、AOP等框架,但对于此类功能后期也可以通过后置处理器扩展。
版权归原作者 阿提说说 所有, 如有侵权,请联系我们删除。