写点什么

基于 Java 的插件化集成项目实践

作者:阿提说说
  • 2022 年 8 月 03 日
  • 本文字数:15630 字

    阅读完需:约 51 分钟

基于Java的插件化集成项目实践

之前已经写了一篇关于《几种 Java 热插拔技术实现总结》,在该文中我总结了好几种 Java 实现热插拔的技术,其中各有优缺点,在这篇文章我将介绍 Java 热插拔技术在我司项目中的实践。

前言

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



  • 插件开发模拟环境

“插件开发模拟环境”主要用于插件的开发和测试,一个独立项目,提供给插件开发人员使用。开发模拟环境依赖插件核心包插件依赖的主程序包。插件核心包-负责插件的加载,安装、注册、卸载插件依赖的主程序包-提供插件开发测试的主程序依赖

  • 主程序

插件的正式安装使用环境,线上环境。插件在本地开发测试完成后,通过插件管理页面安装到线上环境进行插件验证。可以分多个环境,线上 dev 环境提供插件的线上验证,待验证完成后,再发布到 prod 环境。

代码实现

插件加载流程


在监听到 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 插件状态


@AllArgsConstructorpublic enum PluginState {  /**     * 被禁用状态     */    DISABLED("DISABLED"),
/** * 启动状态 */ STARTED("STARTED"),

/** * 停止状态 */ STOPPED("STOPPED"); private final String status;
}
复制代码


RuntimeMode 插件运行环境


@Getter@AllArgsConstructorpublic 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@Builderpublic 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 等


@Slf4jpublic 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")@Datapublic 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 配置插件管理操作类,主程序可以注入该类,操作插件的安装、卸载、获取插件上下文


@Configurationpublic 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;
@Slf4jpublic 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;
@Slf4jpublic 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 等框架,但对于此类功能后期也可以通过后置处理器扩展。

发布于: 41 分钟前阅读数: 12
用户头像

阿提说说

关注

一年太久,只争朝夕 2017.10.19 加入

还未添加个人简介

评论

发布
暂无评论
基于Java的插件化集成项目实践_微服务_阿提说说_InfoQ写作社区