在开始之前,先看下插件系统的整体框架
插件开发模拟环境“插件开发模拟环境”主要用于插件的开发和测试,一个独立项目,提供给插件开发人员使用开发模拟环境依赖 插件核心包 、 插件依赖的主程序包 插件核心包-负责插件的加载,安装、注册、卸载插件依赖的主程序包-提供插件开发测试的主程序依赖
主程序插件的正式安装使用环境,线上环境插件在本地开发测试完成后,通过插件管理页面安装到线上环境进行插件验证可以分多个环境,线上dev环境提供插件的线上验证,待验证完成后,再发布到prod环境代码实现插件加载流程
在监听到Spring Boot启动后,插件开始加载,从配置文件中获取插件配置、创建插件监听器(用于主程序监听插件启动、停止事件,根据事件自定逻辑)、根据获取的插件配置从指定目录加载插件配置信息(插件id、插件版本、插件描述、插件所在路径、插件启动状态(
后期更新 ))、配置信息加载完成后将插件class类注册到Spring返回插件上下文、最后启动完成插件核心包基础常量和类PluginConstants插件常量publicclassPluginConstants。
{publicstaticfinal String TARGET = "target"; publicstaticfinal String POM = "pom.xml"; public
staticfinal String JAR_SUFFIX = ".jar"; publicstaticfinal String REPACKAGE = "repackage";
publicstaticfinal String CLASSES = "classes"; publicstaticfinal String CLASS_SUFFIX = ".class";
publicstaticfinal String MANIFEST = "MANIFEST.MF"; publicstaticfinal String PLUGINID = "pluginId"
; publicstaticfinal String PLUGINVERSION = "pluginVersion"; publicstaticfinal String PLUGINDESCRIPTION =
"pluginDescription"; }PluginState插件状态@AllArgsConstructorpublicenumPluginState{ /** * 被禁用状态 */
DISABLED("DISABLED"), /** * 启动状态 */STARTED("STARTED"), /** * 停止状态 */STOPPED
("STOPPED"); privatefinalString status; }RuntimeMode插件运行环境@Getter@AllArgsConstructor public enum RuntimeMode {
/** * 开发环境 */DEV("dev"), /** * 生产环境 */PROD("prod"); privatefinalStringmode
; publicstaticRuntimeModebyName(String model){ if(DEV.name().equalsIgnoreCase(model)){
returnRuntimeMode.DEV; } else { returnRuntimeMode.PROD; } } }PluginInfo
插件基本信息,重写了hashcode和equals,根据插件id进行去重@Data@Builderpublicclass PluginInfo { /** * 插件id */private
String id; /** * 版本 */privateString version; /** * 描述 */privateString description; /** * 插件路径 */
privateString path; /** * 插件启动状态 */private PluginState pluginState; @Overridepublicboolean equals(
Object obj) { if (this == obj) returntrue; if (obj == null) returnfalse; if (getClass() != obj.getClass())
returnfalse; PluginInfo other = (PluginInfo) obj; return Objects.equals(id, other.id); } @Override
public int hashCode() { return Objects.hash(id); } publicvoid setPluginState(PluginState started) {
this.pluginState = started; } }插件监听器PluginListener插件监听器接口publicinterfacePluginListener{ /** * 注册插件成功 *
@param pluginInfo 插件信息 */defaultvoidstartSuccess(PluginInfo pluginInfo){ } /** * 启动失败 *
@param pluginInfo 插件信息 * @param throwable 异常信息 */defaultvoidstartFailure(PluginInfo pluginInfo, Throwable throwable)
{ } /** * 卸载插件成功 * @param pluginInfo 插件信息 */defaultvoidstopSuccess(PluginInfo pluginInfo)
{ } /** * 停止失败 * @param pluginInfo 插件信息 * @param throwable 异常信息 */defaultvoid
stopFailure(PluginInfo pluginInfo, Throwable throwable){ } }DefaultPluginListenerFactory插件监听工厂,对自定义插件监听器发送事件
publicclassDefaultPluginListenerFactoryimplementsPluginListener{ privatefinal List listeners;
publicDefaultPluginListenerFactory(ApplicationContext applicationContext){ listeners = new ArrayList<>(); addExtendPluginListener(applicationContext); }
publicDefaultPluginListenerFactory(){ listeners = new ArrayList<>(); } privatevoid
addExtendPluginListener(ApplicationContext applicationContext){ Map beansOfTypeMap = applicationContext.getBeansOfType(PluginListener
.class); if (!beansOfTypeMap.isEmpty()) { listeners.addAll(beansOfTypeMap.values()); } }
publicsynchronizedvoidaddPluginListener(PluginListener pluginListener){ if(pluginListener !=
null){ listeners.add(pluginListener); } } public List
getListeners(){ return listeners; } @OverridepublicvoidstartSuccess(PluginInfo pluginInfo)
{ for (PluginListener listener : listeners) { try { listener.startSuccess(pluginInfo); }
catch (Exception e) { } } } @OverridepublicvoidstartFailure
(PluginInfo pluginInfo, Throwable throwable){ for (PluginListener listener : listeners) {
try { listener.startFailure(pluginInfo, throwable); } catch (Exception e) { } } }
@OverridepublicvoidstopSuccess(PluginInfo pluginInfo){ for (PluginListener listener : listeners) {
try { listener.stopSuccess(pluginInfo); } catch (Exception e) { } } }
@OverridepublicvoidstopFailure(PluginInfo pluginInfo, Throwable throwable){ for (PluginListener listener : listeners) {
try { listener.stopFailure(pluginInfo, throwable); } catch (Exception e) { } } } }
DeployUtils部署工具类,读取jar包中的文件,判断class是否为Spring bean等@Slf4j publicclassDeployUtils{ /** * 读取jar包中所有类文件 */
publicstatic Set readJarFile(String jarAddress){ Set classNameSet = new HashSet<>();
try(JarFile jarFile = new JarFile(jarAddress)) { Enumeration 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; } publicstatic InputStream readManifestJarFile(File jarAddress)
{ try { JarFile jarFile = new JarFile(jarAddress); //遍历整个jar文件 Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); String name = jarEntry.getName();
if (name.contains(PluginConstants.MANIFEST)) { return jarFile.getInputStream(jarEntry); } } }
catch (Exception e) { log.warn("加载jar包失败", e); } returnnull; } /** * 方法描述 判断class对象是否带有spring的注解 */
publicstaticbooleanisSpringBeanClass(Class cls){ if (cls == null) { returnfalse; }
//是否是接口if (cls.isInterface()) { returnfalse; } //是否是抽象类if (Modifier.isAbstract(cls.getModifiers())) {
returnfalse; } if (cls.getAnnotation(Component.class) != null) { returntrue; }
if (cls.getAnnotation(Mapper.class) != null) { returntrue; } if (cls.getAnnotation(Service
.class) != null) { returntrue; } if (cls.getAnnotation(RestController.class) != null
) { returntrue; } returnfalse; } publicstaticbooleanisController(Class cls){
if (cls.getAnnotation(Controller.class) != null) { returntrue; } if (cls.getAnnotation(RestController
.class) != null) { returntrue; } returnfalse; } publicstaticbooleanisHaveRequestMapping(Method method)
{ return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null; } /** * 类名首字母小写 作为spring容器beanMap的key */
publicstatic String transformName(String className){ String tmpstr = className.substring(className.lastIndexOf(
".") + 1); return tmpstr.substring(0, 1).toLowerCase() + tmpstr.substring(1); } /** * 读取class文件 *
@param path * @return */publicstatic Set readClassFile(String path){ if (path.endsWith(PluginConstants.JAR_SUFFIX)) {
return readJarFile(path); } else { List pomFiles = FileUtil.loopFiles(path, file -> file.getName().endsWith(PluginConstants.CLASS_SUFFIX)); Set 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")@DatapublicclassPluginAutoConfiguration{ /** * 是否启用插件功能 */
@Value("${enable:true}")privateBoolean enable; /** * 运行模式 * 开发环境: development、dev * 生产/部署 环境: deployment、prod */
@Value("${runMode:dev}")private String runMode; /** * 插件的路径 */private List 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配置插件管理操作类,主程序可以注入该类,操作插件的安装、卸载、获取插件上下文
@ConfigurationpublicclassPluginConfiguration{ @Beanpublic PluginManager createPluginManager(PluginAutoConfiguration configuration, ApplicationContext applicationContext)
{ returnnew 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) publicclassDefaultPluginApplicationextendsAbstractPluginApplication
implementsApplicationContextAware, ApplicationListener { private ApplicationContext applicationContext;
//主程序启动后加载插件@OverridepublicvoidonApplicationEvent(ApplicationStartedEvent event){ super.initialize(applicationContext); }
@OverridepublicvoidsetApplicationContext(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
abstractclassAbstractPluginApplication{ privatefinal AtomicBoolean beInitialized = new AtomicBoolean(
false); publicsynchronizedvoidinitialize(ApplicationContext applicationContext){ Objects.requireNonNull(applicationContext,
"ApplicationContext cant be null"); if(beInitialized.get()) { thrownew 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){ thrownew BeanCreationException("没有发现 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){ thrownew BeanCreationException("没有发现 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 publicclassDefaultPluginManagerimplementsPluginManager{ private PluginAutoConfiguration pluginAutoConfiguration;
private ApplicationContext applicationContext; private DefaultPluginListenerFactory pluginListenerFactory;
private PluginClassRegister pluginClassRegister; private Map pluginBeans =
new ConcurrentHashMap<>(); private Map pluginInfoMap = new ConcurrentHashMap<>();
privatefinal AtomicBoolean loaded = new AtomicBoolean(false); publicDefaultPluginManager(PluginAutoConfiguration pluginAutoConfiguration, ApplicationContext applicationContext)
{ this.pluginAutoConfiguration = pluginAutoConfiguration; this.applicationContext = applicationContext;
this.pluginClassRegister = new PluginClassRegister(applicationContext, pluginAutoConfiguration, pluginBeans); }
publicvoidcreatePluginListenerFactory(){ this.pluginListenerFactory = new DefaultPluginListenerFactory(applicationContext); }
@Overridepublic List loadPlugins()throws Exception { if(loaded.get()){ thrownew PluginException(
"不能重复调用: loadPlugins"); } //从配置路径获取插件目录//解析插件jar包中的配置,生成配置对象 List 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 loadPluginsFromPath(List pluginPath)throws
IOException, XmlPullParserException { List pluginInfoList = new ArrayList<>(); for
(String path : pluginPath) { Path resolvePath = Paths.get(path); Set pluginInfos = buildPluginInfo(resolvePath); pluginInfoList.addAll(pluginInfos); }
return pluginInfoList; } private Set buildPluginInfo(Path path)throws IOException, XmlPullParserException
{ Set pluginInfoList = new HashSet<>(); //开发环境if (RuntimeMode.DEV == pluginAutoConfiguration.environment()) { List 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 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; }
@Overridepublic PluginInfo install(Path pluginPath){ if (RuntimeMode.PROD != pluginAutoConfiguration.environment()) {
thrownew PluginException("插件安装只适用于生产环境"); } try { Set pluginInfos = buildPluginInfo(pluginPath);
if (CollUtil.isEmpty(pluginInfos)) { thrownew 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) { thrownew PluginException("插件安装失败", e); } } private
voidstart(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
publicvoiduninstall(String pluginId){ if (RuntimeMode.PROD != pluginAutoConfiguration.environment()) {
thrownew PluginException("插件卸载只适用于生产环境"); } PluginInfo pluginInfo = pluginInfoMap.get(pluginId);
if (pluginInfo == null) { return; } stop(pluginInfo); backupPlugin(pluginInfo); clear(pluginInfo); }
@Overridepublic PluginInfo start(String pluginId){ PluginInfo pluginInfo = pluginInfoMap.get(pluginId); start(pluginInfo);
return pluginInfo; } @Overridepublic PluginInfo stop(String pluginId){ PluginInfo pluginInfo = pluginInfoMap.get(pluginId); stop(pluginInfo);
return pluginInfo; } privatevoidclear(PluginInfo pluginInfo){ PathUtil.del(Paths.get(pluginInfo.getPath())); pluginInfoMap.remove(pluginInfo.getId()); }
privatevoidstop(PluginInfo pluginInfo){ try { pluginClassRegister.unRegister(pluginInfo); pluginInfo.setPluginState(PluginState.STOPPED); pluginListenerFactory.stopSuccess(pluginInfo); log.info(
"插件{}停止成功", pluginInfo.getId()); } catch (Exception e) { log.error("插件{}停止异常", pluginInfo.getId(), e); } }
privatevoidbackupPlugin(PluginInfo pluginInfo){ String backupPath = pluginAutoConfiguration.getBackupPath();
if (CharSequenceUtil.isBlank(backupPath)) { return; } String newName = pluginInfo.getId() + DateUtil.now() + PluginConstants.JAR_SUFFIX; String newPath = backupPath + File.separator + newName; FileUtil.copyFile(pluginInfo.getPath(), newPath); }
@Overridepublic ApplicationContext getApplicationContext(String pluginId){ return pluginBeans.get(pluginId); }
@Overridepublic List getBeansWithAnnotation(String pluginId, Class annotationType)
{ ApplicationContext pluginApplicationContext = pluginBeans.get(pluginId); if(pluginApplicationContext !=
null){ Map beanMap = pluginApplicationContext.getBeansWithAnnotation(annotationType);
returnnew ArrayList<>(beanMap.values()); } returnnew ArrayList<>(0); } }PluginClassRegister插件动态注册、动态卸载,解析插件class,判断是否为Spring Bean或Spring 接口,是注册到Spring 中
publicclassPluginClassRegister{ private ApplicationContext applicationContext; private RequestMappingHandlerMapping requestMappingHandlerMapping;
private Method getMappingForMethod; private PluginAutoConfiguration configuration; private Map pluginBeans;
private Map requestMappings = new ConcurrentHashMap<>(); publicPluginClassRegister
(ApplicationContext applicationContext, PluginAutoConfiguration configuration, Map 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; } publicbooleanunRegister(PluginInfo pluginInfo){ return unRegisterBean(pluginInfo); }
privatebooleanunRegisterBean(PluginInfo pluginInfo){ GenericWebApplicationContext pluginApplicationContext = (GenericWebApplicationContext) pluginBeans.get(pluginInfo.getId()); pluginApplicationContext.close();
//取消注册controller Set requestMappingInfoSet = requestMappings.get(pluginInfo.getId());
if (requestMappingInfoSet != null) { requestMappingInfoSet.forEach(this::unRegisterController); } requestMappings.remove(pluginInfo.getId()); pluginBeans.remove(pluginInfo.getId());
returntrue; } privatevoidunRegisterController(RequestMappingInfo requestMappingInfo){ requestMappingHandlerMapping.unregisterMapping(requestMappingInfo); }
private ApplicationContext registerBean(PluginInfo pluginInfo){ String path = pluginInfo.getPath(); Set 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 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 pluginRequestMappings =
new HashSet<>(); for (String beanName : beanNames) { //注入bean Object bean = pluginApplicationContext.getBean(beanName); injectService(bean);
//注册接口 Set requestMappingInfos = registerController(bean); requestMappingInfos.forEach(requestMappingInfo -> { log.info(
"插件{}注册接口{}", pluginInfo.getId(), requestMappingInfo); }); pluginRequestMappings.addAll(requestMappingInfos); } requestMappings.put(pluginInfo.getId(), pluginRequestMappings);
return pluginApplicationContext; } catch (Exception e) { thrownew PluginException("注册bean异常", e); }
finally { try { if (classLoader != null) { classLoader.close(); } } catch (IOException e) { log.error(
"classLoader关闭失败", e); } } } private Set registerController(Object bean){ Class aClass = bean.getClass(); Set 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; }
privatevoidinjectService(Object instance){ if (instance==null) { return; } Field[] fields = ReflectUtil.getFields(instance.getClass());
//instance.getClass().getDeclaredFields();for (Field field : fields) { if (Modifier.isStatic(field.getModifiers())) {
continue; } Object fieldBean = null; // with bean-id, bean could be found by both @Resource and @Autowired, or bean could only be found by @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等框架,但对于此类功能后期也可以通过后置处理器扩展。
亲爱的读者们,感谢您花时间阅读本文。如果您对本文有任何疑问或建议,请随时联系我。我非常乐意与您交流。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。