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

wufei123 发布于 2024-08-30 阅读(5)

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

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

主程序插件的正式安装使用环境,线上环境插件在本地开发测试完成后,通过插件管理页面安装到线上环境进行插件验证可以分多个环境,线上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等框架,但对于此类功能后期也可以通过后置处理器扩展。

亲爱的读者们,感谢您花时间阅读本文。如果您对本文有任何疑问或建议,请随时联系我。我非常乐意与您交流。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。