基于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;
在开始之前,先看下插件系统的整体框架

- 插件开发模拟环境
“插件开发模拟环境”主要用于插件的开发和测试,一个独立项目,提供给插件开发人员使用。开发模拟环境依赖 插件核心包 、 插件依赖的主程序包 。
插件核心包-负责插件的加载,安装、注册、卸载
插件依赖的主程序包-提供插件开发测试的主程序依赖 - 主程序
插件的正式安装使用环境,线上环境。插件在本地开发测试完成后,通过插件管理页面安装到线上环境进行插件验证。可以分多个环境,线上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
插件状态
@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等框架,但对于此类功能后期也可以通过后置处理器扩展。




