基于 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 插件状态
@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 等框架,但对于此类功能后期也可以通过后置处理器扩展。
版权声明: 本文为 InfoQ 作者【阿提说说】的原创文章。
原文链接:【http://xie.infoq.cn/article/61fa047b781c3d3af31f00e69】。文章转载请联系作者。
阿提说说
一年太久,只争朝夕 2017.10.19 加入
还未添加个人简介
评论