基于 Java 的插件化集成项目实践
- 2022 年 8 月 03 日
- 本文字数:15630 字 - 阅读完需:约 51 分钟 

之前已经写了一篇关于《几种 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 等框架,但对于此类功能后期也可以通过后置处理器扩展。
版权声明: 本文为 InfoQ 作者【阿提说说】的原创文章。
原文链接:【http://xie.infoq.cn/article/61fa047b781c3d3af31f00e69】。文章转载请联系作者。

阿提说说
一年太久,只争朝夕 2017.10.19 加入
还未添加个人简介










 
    
评论