快捷搜索:  汽车  科技

基于java 后台极速开发框架(基于Java的插件化集成项目实践)

基于java 后台极速开发框架(基于Java的插件化集成项目实践)@Getter @AllArgsConstructor public enum RuntimeMode { /** * 开发环境 */ DEV("dev") /** * 生产环境 */ PROD("prod"); private final String mode; public static RuntimeMode byName(String model){ if(DEV.name().equalsIgnoreCase(model)){ return RuntimeMode.DEV; } else { return RuntimeMode.PROD;

在开始之前,先看下插件系统的整体框架

基于java 后台极速开发框架(基于Java的插件化集成项目实践)(1)

  • 插件开发模拟环境
    “插件开发模拟环境”主要用于插件的开发和测试,一个独立项目,提供给插件开发人员使用。开发模拟环境依赖 插件核心包插件依赖的主程序包
    插件核心包-负责插件的加载,安装、注册、卸载
    插件依赖的主程序包-提供插件开发测试的主程序依赖
  • 主程序
    插件的正式安装使用环境,线上环境。插件在本地开发测试完成后,通过插件管理页面安装到线上环境进行插件验证。可以分多个环境,线上DEV环境提供插件的线上验证,待验证完成后,再发布到prod环境。
代码实现插件加载流程

基于java 后台极速开发框架(基于Java的插件化集成项目实践)(2)

在监听到Spring Boot启动后,插件开始加载,从配置文件中获取插件配置、创建插件监听器(用于主程序监听插件启动、停止事件,根据事件自定逻辑)、根据获取的插件配置从指定目录加载插件配置信息(插件id、插件版本、插件描述、插件所在路径、插件启动状态( 后期更新 ))、配置信息加载完成后将插件class类注册到Spring返回插件上下文、最后启动完成。

插件核心包基础常量和类

PluginConstants

插件常量

public class PluginConstants { public static final String TARGET = "target"; public static final String POM = "pom.xml"; public static final String JAR_SUFFIX = ".Jar"; public static final String REPACKAGE = "repackage"; public static final String CLASSES = "classes"; public static final String CLASS_SUFFIX = ".class"; public static final String MANIFEST = "MANIFEST.MF"; public static final String PLUGINID = "pluginId"; public static final String PluginVERSION = "pluginVersion"; public static final String PLUGINDESCRIPTION = "pluginDescription"; }

PluginState

插件状态

@AllArgsConstructor public enum PluginState { /** * 被禁用状态 */ DISABLED("DISABLED") /** * 启动状态 */ STARTED("STARTED") /** * 停止状态 */ STOPPED("STOPPED"); private final String status; }

RuntimeMode

插件运行环境

@Getter @AllArgsConstructor public enum RuntimeMode { /** * 开发环境 */ DEV("dev") /** * 生产环境 */ PROD("prod"); private final String mode; public static RuntimeMode byName(String model){ if(DEV.name().equalsIgnoreCase(model)){ return RuntimeMode.DEV; } else { return RuntimeMode.PROD; } } }

PluginInfo

插件基本信息,重写了hashCode和equals,根据插件id进行去重

@Data @Builder public class PluginInfo { /** * 插件id */ private String id; /** * 版本 */ private String version; /** * 描述 */ private String description; /** * 插件路径 */ private String path; /** * 插件启动状态 */ private PluginState pluginState; @Override public Boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PluginInfo other = (PluginInfo) obj; return Objects.equals(id other.id); } @Override public int hashCode() { return Objects.hash(id); } public void setPluginState(PluginState started) { this.pluginState = started; } }插件监听器

PluginListener

插件监听器接口

public interface PluginListener { /** * 注册插件成功 * @param pluginInfo 插件信息 */ default void startSuccess(PluginInfo pluginInfo){ } /** * 启动失败 * @param pluginInfo 插件信息 * @param throwable 异常信息 */ default void startFailure(PluginInfo pluginInfo Throwable throwable){ } /** * 卸载插件成功 * @param pluginInfo 插件信息 */ default void stopSuccess(PluginInfo pluginInfo){ } /** * 停止失败 * @param pluginInfo 插件信息 * @param throwable 异常信息 */ default void stopFailure(PluginInfo pluginInfo Throwable throwable){ } }

DefaultPluginListenerFactory

插件监听工厂,对自定义插件监听器发送事件

public class DefaultPluginListenerFactory implements PluginListener { private final List<PluginListener> listeners; public DefaultPluginListenerFactory(ApplicationContext applicationContext){ listeners = new ArrayList<>(); addExtendPluginListener(applicationContext); } public DefaultPluginListenerFactory(){ listeners = new ArrayList<>(); } private void addExtendPluginListener(ApplicationContext applicationContext){ Map<String PluginListener> beansOfTypeMap = applicationContext.getBeansOfType(PluginListener.class); if (!beansOfTypeMap.isEmpty()) { listeners.addAll(beansOfTypeMap.values()); } } public synchronized void addPluginListener(PluginListener pluginListener) { if(pluginListener != null){ listeners.add(pluginListener); } } public List<PluginListener> getListeners() { return listeners; } @Override public void startSuccess(PluginInfo pluginInfo) { for (PluginListener listener : listeners) { try { listener.startSuccess(pluginInfo); } catch (Exception e) { } } } @Override public void startFailure(PluginInfo pluginInfo Throwable throwable) { for (PluginListener listener : listeners) { try { listener.startFailure(pluginInfo throwable); } catch (Exception e) { } } } @Override public void stopSuccess(PluginInfo pluginInfo) { for (PluginListener listener : listeners) { try { listener.stopSuccess(pluginInfo); } catch (Exception e) { } } } @Override public void stopFailure(PluginInfo pluginInfo Throwable throwable) { for (PluginListener listener : listeners) { try { listener.stopFailure(pluginInfo throwable); } catch (Exception e) { } } } }

DeployUtils

部署工具类,读取jar包中的文件,判断class是否为Spring bean等

@Slf4j public class DeployUtils { /** * 读取jar包中所有类文件 */ public static Set<String> readJarFile(String jarAddress) { Set<String> classNameSet = new HashSet<>(); try(JarFile jarFile = new JarFile(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; } public static InputStream readManifestJarFile(File jarAddress) { try { JarFile jarFile = new JarFile(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); } return null; } /** * 方法描述 判断class对象是否带有spring的注解 */ public static boolean isSpringBeanClass(Class<?> cls) { if (cls == null) { return false; } //是否是接口 if (cls.isInterface()) { return false; } //是否是抽象类 if (Modifier.isAbstract(cls.getModifiers())) { return false; } if (cls.getAnnotation(Component.class) != null) { return true; } if (cls.getAnnotation(Mapper.class) != null) { return true; } if (cls.getAnnotation(Service.class) != null) { return true; } if (cls.getAnnotation(RestController.class) != null) { return true; } return false; } public static boolean isController(Class<?> cls) { if (cls.getAnnotation(Controller.class) != null) { return true; } if (cls.getAnnotation(RestController.class) != null) { return true; } return false; } public static boolean isHaveRequestMapping(Method method) { return AnnotationUtils.findAnnotation(method RequestMapping.class) != null; } /** * 类名首字母小写 作为spring容器beanMap的key */ public static String transformName(String className) { String tmpstr = className.substring(className.lastIndexOf(".") 1); return tmpstr.substring(0 1).toLowerCase() tmpstr.substring(1); } /** * 读取class文件 * @param path * @return */ public static Set<String> readClassFile(String path) { if (path.endsWith(PluginConstants.JAR_SUFFIX)) { return readJarFile(path); } else { List<File> pomFiles = FileUtil.loopFiles(path file -> file.getName().endsWith(PluginConstants.CLASS_SUFFIX)); Set<String> classNameSet = new HashSet<>(); 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") @Data public class PluginAutoConfiguration { /** * 是否启用插件功能 */ @Value("${enable:true}") private Boolean enable; /** * 运行模式 * 开发环境: development、dev * 生产/部署 环境: deployment、prod */ @Value("${runMode:dev}") private String runMode; /** * 插件的路径 */ private List<String> pluginPath; /** * 在卸载插件后 备份插件的目录 */ @Value("${backupPath:backupPlugin}") private String backupPath; public RuntimeMode environment() { return RuntimeMode.byName(runMode); } }

PluginStarter

插件自动化配置,配置在spring.factories中

@Configuration(proxyBeanMethods = true) @EnableConfigurationProperties(PluginAutoConfiguration.class) @Import(DefaultPluginApplication.class) public class PluginStarter { }

PluginConfiguration

配置插件管理操作类,主程序可以注入该类,操作插件的安装、卸载、获取插件上下文

@Configuration public class PluginConfiguration { @Bean public PluginManager createPluginManager(PluginAutoConfiguration configuration ApplicationContext applicationContext) { return new DefaultPluginManager(configuration applicationContext); } }插件加载注册

DefaultPluginApplication

监听Spring Boot启动完成,加载插件,调用父类的加载方法,获取主程序上下文

import org.springframework.beans.BeansException; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Import; @Import(PluginConfiguration.class) public class DefaultPluginApplication extends AbstractPluginApplication implements ApplicationContextAware ApplicationListener<ApplicationStartedEvent> { private ApplicationContext applicationContext; //主程序启动后加载插件 @Override public void onApplicationEvent(ApplicationStartedEvent event) { super.initialize(applicationContext); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }

AbstractPluginApplication

提供插件的加载,从主程序中获取插件配置,获取插件管理操作类

import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ApplicationContext; import lombok.extern.slf4j.Slf4j; @Slf4j public abstract class AbstractPluginApplication { private final AtomicBoolean beInitialized = new AtomicBoolean(false); public synchronized void initialize(ApplicationContext applicationContext) { Objects.requireNonNull(applicationContext "ApplicationContext can't be null"); if(beInitialized.get()) { throw new RuntimeException("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); } } protected PluginAutoConfiguration getConfiguration(ApplicationContext applicationContext) { PluginAutoConfiguration configuration = null; try { configuration = applicationContext.getBean(PluginAutoConfiguration.class); } catch (Exception e){ // no show exception } if(configuration == null){ throw new BeanCreationException("没有发现 <PluginAutoConfiguration> Bean"); } return configuration; } protected DefaultPluginManager getPluginManager(ApplicationContext applicationContext) { DefaultPluginManager pluginManager = null; try { pluginManager = applicationContext.getBean(DefaultPluginManager.class); } catch (Exception e){ // no show exception } if(pluginManager == null){ throw new BeanCreationException("没有发现 <DefaultPluginManager> Bean"); } return pluginManager; } }

DefaultPluginManager

插件操作类,管理插件的加载、安装、卸载,主程序使用该类对插件进行操作

import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.jar.Attributes; import java.util.jar.Manifest; import org.apache.maven.model.Model; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.springframework.context.ApplicationContext; import com.greentown.plugin.constants.PluginConstants; import com.greentown.plugin.constants.PluginState; import com.greentown.plugin.constants.RuntimeMode; import com.greentown.plugin.listener.DefaultPluginListenerFactory; import com.greentown.plugin.util.DeployUtils; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.file.PathUtil; import cn.hutool.core.text.CharSequenceUtil; import lombok.extern.slf4j.Slf4j; @Slf4j public class DefaultPluginManager implements PluginManager { private PluginAutoConfiguration pluginAutoConfiguration; private ApplicationContext applicationContext; private DefaultPluginListenerFactory pluginListenerFactory; private PluginClassRegister pluginClassRegister; private Map<String ApplicationContext> pluginBeans = new ConcurrentHashMap<>(); private Map<String PluginInfo> pluginInfoMap = new ConcurrentHashMap<>(); private final AtomicBoolean loaded = new AtomicBoolean(false); public DefaultPluginManager(PluginAutoConfiguration pluginAutoConfiguration ApplicationContext applicationContext) { this.pluginAutoConfiguration = pluginAutoConfiguration; this.applicationContext = applicationContext; this.pluginClassRegister = new PluginClassRegister(applicationContext pluginAutoConfiguration pluginBeans); } public void createPluginListenerFactory() { this.pluginListenerFactory = new DefaultPluginListenerFactory(applicationContext); } @Override public List<PluginInfo> loadPlugins() throws Exception { if(loaded.get()){ throw new PluginException("不能重复调用: 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; } private List<PluginInfo> loadPluginsFromPath(List<String> pluginPath) throws IOException XmlPullParserException { List<PluginInfo> pluginInfoList = new ArrayList<>(); for (String path : pluginPath) { Path resolvePath = Paths.get(path); Set<PluginInfo> pluginInfos = buildPluginInfo(resolvePath); pluginInfoList.addAll(pluginInfos); } return pluginInfoList; } private Set<PluginInfo> buildPluginInfo(Path path) throws IOException XmlPullParserException { Set<PluginInfo> pluginInfoList = new HashSet<>(); //开发环境 if (RuntimeMode.DEV == pluginAutoConfiguration.environment()) { List<File> pomFiles = FileUtil.loopFiles(path.toString() file -> PluginConstants.POM.equals(file.getName())); for (File file : pomFiles) { MavenXpp3Reader reader = new MavenXpp3Reader(); Model model = reader.read(new FileInputStream(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 = new Manifest(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; } @Override public PluginInfo install(Path pluginPath) { if (RuntimeMode.PROD != pluginAutoConfiguration.environment()) { throw new PluginException("插件安装只适用于生产环境"); } try { Set<PluginInfo> pluginInfos = buildPluginInfo(pluginPath); if (CollUtil.isEmpty(pluginInfos)) { throw new PluginException("插件不存在"); } 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) { throw new PluginException("插件安装失败" e); } } private void start(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); } } @Override public void uninstall(String pluginId) { if (RuntimeMode.PROD != pluginAutoConfiguration.environment()) { throw new PluginException("插件卸载只适用于生产环境"); } PluginInfo pluginInfo = pluginInfoMap.get(pluginId); if (pluginInfo == null) { return; } stop(pluginInfo); backupPlugin(pluginInfo); clear(pluginInfo); } @Override public PluginInfo start(String pluginId) { PluginInfo pluginInfo = pluginInfoMap.get(pluginId); start(pluginInfo); return pluginInfo; } @Override public PluginInfo stop(String pluginId) { PluginInfo pluginInfo = pluginInfoMap.get(pluginId); stop(pluginInfo); return pluginInfo; } private void clear(PluginInfo pluginInfo) { PathUtil.del(Paths.get(pluginInfo.getPath())); pluginInfoMap.remove(pluginInfo.getId()); } private void stop(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); } } private void backupPlugin(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); } @Override public ApplicationContext getApplicationContext(String pluginId) { return pluginBeans.get(pluginId); } @Override public List<Object> getBeansWithAnnotation(String pluginId Class<? extends Annotation> annotationType) { ApplicationContext pluginApplicationContext = pluginBeans.get(pluginId); if(pluginApplicationContext != null){ Map<String Object> beanMap = pluginApplicationContext.getBeansWithAnnotation(annotationType); return new ArrayList<>(beanMap.values()); } return new ArrayList<>(0); } }

PluginClassRegister

插件动态注册、动态卸载,解析插件class,判断是否为Spring Bean或Spring 接口,是注册到Spring 中

public class PluginClassRegister { private ApplicationContext applicationContext; private RequestMappingHandlerMapping requestMappingHandlerMapping; private Method getMappingForMethod; private PluginAutoConfiguration configuration; private Map<String ApplicationContext> pluginBeans; private Map<String Set<RequestMappingInfo>> requestMappings = new ConcurrentHashMap<>(); public PluginClassRegister(ApplicationContext applicationContext PluginAutoConfiguration configuration Map<String ApplicationContext> pluginBeans) { this.applicationContext = applicationContext; this.requestMappingHandlerMapping = getRequestMapping(); this.getMappingForMethod = getRequestMethod(); this.configuration = configuration; this.pluginBeans = pluginBeans; } public ApplicationContext register(PluginInfo pluginInfo) { ApplicationContext pluginApplicationContext = registerBean(pluginInfo); pluginBeans.put(pluginInfo.getId() pluginApplicationContext); return pluginApplicationContext; } public boolean unRegister(PluginInfo pluginInfo) { return unRegisterBean(pluginInfo); } private boolean unRegisterBean(PluginInfo pluginInfo) { GenericWebApplicationContext pluginApplicationContext = (GenericWebApplicationContext) pluginBeans.get(pluginInfo.getId()); pluginApplicationContext.close(); //取消注册controller Set<RequestMappingInfo> requestMappingInfoSet = requestMappings.get(pluginInfo.getId()); if (requestMappingInfoSet != null) { requestMappingInfoSet.forEach(this::unRegisterController); } requestMappings.remove(pluginInfo.getId()); pluginBeans.remove(pluginInfo.getId()); return true; } private void unRegisterController(RequestMappingInfo requestMappingInfo) { requestMappingHandlerMapping.unregisterMapping(requestMappingInfo); } private ApplicationContext registerBean(PluginInfo pluginInfo) { String path = pluginInfo.getPath(); Set<String> classNames = DeployUtils.readClassFile(path); URLClassLoader classLoader = null; try { //class 加载器 URL jarURL = new File(path).toURI().toURL(); classLoader = new URLClassLoader(new URL[] { jarURL } Thread.currentThread().getContextClassLoader()); //一个插件创建一个applicationContext GenericWebApplicationContext pluginApplicationContext = new GenericWebApplicationContext(); pluginApplicationContext.setResourceLoader(new DefaultResourceLoader(classLoader)); //注册bean List<String> beanNames = new ArrayList<>(); 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 = new HashSet<>(); for (String beanName : beanNames) { //注入bean Object 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) { throw new PluginException("注册bean异常" e); } finally { try { if (classLoader != null) { classLoader.close(); } } catch (IOException e) { log.error("classLoader关闭失败" e); } } } private Set<RequestMappingInfo> registerController(Object bean) { Class<?> aClass = bean.getClass(); Set<RequestMappingInfo> requestMappingInfos = new HashSet<>(); 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; } private void injectService(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 @Autowired if (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()); } } else if (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); } } } } private Method getRequestMethod() { try { Method method = ReflectUtils.findDeclaredMethod(requestMappingHandlerMapping.getClass() "getMappingForMethod" new Class[] { Method.class Class.class }); method.setAccessible(true); return method; } catch (Exception ex) { log.error("反射获取detectHandlerMethods异常" ex); } return null; } private RequestMappingHandlerMapping getRequestMapping() { return (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping"); } }插件Mock包

plugin-mock

提供插件的开发模拟测试相关的依赖,以Jar包方式提供,根据具体项目提供依赖

插件开发环境

一个独立的项目,依赖上述提供的插件核心包、插件Mock包,提供给插件开发人员使用。

main-application:插件开发测试的主程序

plugins:插件开发目录

在最开始的使用,我们的插件使用Spring Brick来开发,光在集成过程中就发现不少问题,特别是依赖冲突很多,并且对插件的加载比较慢,导致主程序启动慢。

在自研插件后,该插件加载启动使用动态注入Spring的方式,相比较Spring Brick的插件独立Spring Boot方式加载速度更快,占用内存更小,虽然还不支持Freemark、AOP等框架,但对于此类功能后期也可以通过后置处理器扩展。

猜您喜欢: