diff --git a/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java b/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java index 08a6797..bf01d84 100644 --- a/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java +++ b/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java @@ -1,6 +1,5 @@ package com.cool.core.plugin.config; -import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.Map; @@ -33,23 +32,21 @@ public class DynamicJarClassLoader extends URLClassLoader { } public void unload() { - try { - for (Map.Entry> entry : loadedClasses.entrySet()) { - // 从已加载的类集合中移除该类 - String className = entry.getKey(); - loadedClasses.remove(className); + new Thread(new Runnable() { + @Override + public void run() { try { - // 调用该类的destory方法,回收资源 - Class clazz = entry.getValue(); - Method destory = clazz.getDeclaredMethod("destory"); - destory.invoke(clazz); - } catch (NoClassDefFoundError | Exception ignored) { + for (Map.Entry> entry : loadedClasses.entrySet()) { + // 从已加载的类集合中移除该类 + String className = entry.getKey(); + loadedClasses.remove(className); + } + // 从其父类加载器的加载器层次结构中移除该类加载器 + close(); + } catch (Exception e) { + log.error("unload error", e); } } - // 从其父类加载器的加载器层次结构中移除该类加载器 - close(); - } catch (Exception e) { - log.error("unload error", e); - } + }).start(); } } \ No newline at end of file diff --git a/src/main/java/com/cool/core/plugin/service/CoolPluginService.java b/src/main/java/com/cool/core/plugin/service/CoolPluginService.java index 260446d..6c7766f 100644 --- a/src/main/java/com/cool/core/plugin/service/CoolPluginService.java +++ b/src/main/java/com/cool/core/plugin/service/CoolPluginService.java @@ -67,8 +67,8 @@ public class CoolPluginService { File file = new File(pluginJson.getJarPath()); // 检查路径是否存在 if (!file.exists()) { - PluginInfoEntity pluginInfoEntity = pluginInfoService.getById(entity.getId()); - FileUtil.writeBytes(pluginInfoEntity.getJarFile(), file); + log.warn("插件不存在,请重新安装!"); + return; } file = new File(pluginJson.getJarPath()); if (file.exists()) { @@ -100,32 +100,38 @@ public class CoolPluginService { PluginJson pluginJson = dynamicJarLoaderService.install(jarFilePath, force); key = pluginJson.getKey(); // 保存插件信息入库 - savePluginInfo(pluginJson, jarFilePath, jarFile, force); + savePluginInfo(pluginJson, jarFilePath, force); // 把 ApplicationContext 对象传递打插件类中,使其在插件中也能正常使用spring bean对象 CoolPluginInvokers.setApplicationContext(pluginJson.getKey()); } catch (PersistenceException persistenceException) { + extractedAfterErr(jarFile, key); if (persistenceException.getMessage().contains("Duplicate entry")) { // 唯一键冲突 CoolPreconditions.returnData( new CoolPreconditions.ReturnData(1, "插件已存在,继续安装将覆盖")); } - if (ObjUtil.isNotEmpty(key)) { - // 报错失败,调用卸载 - dynamicJarLoaderService.uninstall(key); - } + CoolPreconditions.alwaysThrow(persistenceException.getMessage()); } catch (CoolException e) { - FileUtil.del(jarFile); + extractedAfterErr(jarFile, key); throw e; } catch (Exception e) { - FileUtil.del(jarFile); log.error("插件安装失败", e); + extractedAfterErr(jarFile, key); CoolPreconditions.alwaysThrow("插件安装失败", e); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } } + private void extractedAfterErr(File jarFile, String key) { + FileUtil.del(jarFile); + if (ObjUtil.isNotEmpty(key)) { + // 报错失败,调用卸载 + dynamicJarLoaderService.uninstall(key); + } + } + /** * 保存jar文件 */ @@ -153,7 +159,7 @@ public class CoolPluginService { * 卸载 */ public void uninstall(Long id) { - PluginInfoEntity pluginInfoEntity = pluginInfoService.getPluginInfoEntityByIdNoJarFile(id); + PluginInfoEntity pluginInfoEntity = pluginInfoService.getById(id); CoolPreconditions.checkEmpty(pluginInfoEntity, "插件不存在"); if (dynamicJarLoaderService.uninstall(pluginInfoEntity.getKey())) { boolean flag = pluginInfoEntity.removeById(); @@ -166,7 +172,7 @@ public class CoolPluginService { /** * 保存插件信息 */ - private void savePluginInfo(PluginJson pluginJson, String jarFilePath, File jarFile, + private void savePluginInfo(PluginJson pluginJson, String jarFilePath , boolean force) { CoolPreconditions.checkEmpty(pluginJson, "插件安装失败"); pluginJson.setJarPath(jarFilePath); @@ -175,8 +181,6 @@ public class CoolPluginService { setLogoOrReadme(pluginJson, pluginInfo); pluginInfo.setKey(pluginJson.getKey()); pluginInfo.setPluginJson(pluginJson); - // 转二进制 - pluginInfo.setJarFile(FileUtil.readBytes(jarFile)); if (force) { // 判断是否有同名插件, 有将其关闭 closeSameNamePlugin(pluginJson); @@ -192,7 +196,7 @@ public class CoolPluginService { */ private void coverPlugin(PluginJson pluginJson, PluginInfoEntity pluginInfo) { // 通过key 找到id - PluginInfoEntity one = pluginInfoService.getByKeyNoJarFile(pluginJson.getKey()); + PluginInfoEntity one = pluginInfoService.getByKey(pluginJson.getKey()); if (ObjUtil.isNotEmpty(one)) { String oldJarPath = one.getPluginJson().getJarPath(); // 重新加载配置不更新 @@ -204,6 +208,7 @@ public class CoolPluginService { // 忽略无变更,无需更新的字段 ignoreNoChange(pluginInfo, one); BeanUtil.copyProperties(pluginInfo, one, options); + one.setStatus(1); if (one.updateById()) { // 覆盖时删除旧版本插件 FileUtil.del(oldJarPath); @@ -263,7 +268,7 @@ public class CoolPluginService { } public void updatePlugin(PluginInfoEntity entity) { - PluginInfoEntity dbPluginInfoEntity = pluginInfoService.getPluginInfoEntityByIdNoJarFile( + PluginInfoEntity dbPluginInfoEntity = pluginInfoService.getById( entity.getId()); // 调用插件更新配置标识 boolean invokePluginConfig = false; @@ -295,7 +300,7 @@ public class CoolPluginService { if (ObjUtil.isNotEmpty(dbPluginInfoEntity.getHook())) { // 查找是否有同名hook,有同名hook,如果状态为开启不允许在开启,需先关闭原来 PluginInfoEntity hookPlugin = pluginInfoService - .getPluginInfoEntityByHookNoJarFile(dbPluginInfoEntity.getHook()); + .getPluginInfoEntityByHook(dbPluginInfoEntity.getHook()); if (ObjUtil.isNotEmpty(hookPlugin)) { CoolPreconditions.check( !ObjUtil.equals(hookPlugin.getKey(), dbPluginInfoEntity.getKey()) @@ -317,7 +322,7 @@ public class CoolPluginService { * 通过hook获取插件 */ public PluginInfoEntity getPluginInfoEntityByHook(String hook) { - return pluginInfoService.getPluginInfoEntityByHookNoJarFile(hook); + return pluginInfoService.getPluginInfoEntityByHook(hook); } /** diff --git a/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java b/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java index 97a24df..4d62b43 100644 --- a/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java +++ b/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java @@ -16,8 +16,9 @@ import java.io.File; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; -import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -46,15 +47,62 @@ public class DynamicJarLoaderService { DynamicJarClassLoader dynamicJarClassLoader = new DynamicJarClassLoader(new URL[]{jarUrl}, Thread.currentThread().getContextClassLoader()); Thread.currentThread().setContextClassLoader(dynamicJarClassLoader); + PluginJson pluginJson = getPluginJsonAndCheck(force, dynamicJarClassLoader); + // 加载类 + List> plugins = new ArrayList<>(); + int count = 0; + uninstall(pluginJson.getKey()); + Instant start = Instant.now(); + int progressThreshold = 10; // 输出进度的阈值为10% + try (JarFile jarFile = ((JarURLConnection) jarUrl.openConnection()).getJarFile()) { + List list = jarFile.stream().toList(); + int size = list.size(); + int currentProgress = 0; + for (JarEntry jarEntry : list) { + count++; + loadClass(jarEntry, dynamicJarClassLoader, plugins); + // 计算进度百分比 + int progress = (int) ((count / (double) size) * 100); + // 输出一次进度 + if (progress % progressThreshold == 0 && currentProgress != progress) { + log.info("安装进度: {}%", progress); + currentProgress = progress; + } + } + } finally{ + Instant end = Instant.now(); + Duration timeElapsed = Duration.between(start, end); + log.info("本次共加载{}个文件 耗时: {}ms", count, timeElapsed.toMillis()); + } + // 校验插件 + checkPlugin(plugins); + registerPlugin(pluginJson.getKey(), plugins.get(0), dynamicJarClassLoader, force); + dynamicJarClassLoaderMap.put(pluginJson.getKey(), dynamicJarClassLoader); + + log.info("插件{}安装成功.", pluginJson.getKey()); + return pluginJson; + } + + /** + * 校验,获取插件配置 + */ + private PluginJson getPluginJsonAndCheck(boolean force, DynamicJarClassLoader dynamicJarClassLoader) { InputStream inputStream = dynamicJarClassLoader.getResourceAsStream("plugin.json"); CoolPreconditions.check(ObjUtil.isEmpty(inputStream), "不合规插件:未找到plugin.json文件"); String pluginJsonStr = StrUtil.str(IoUtil.readBytes(inputStream), "UTF-8"); PluginJson pluginJson = JSONUtil.toBean(pluginJsonStr, PluginJson.class); CoolPreconditions.check(ObjUtil.isEmpty(pluginJson.getKey()), "该插件缺少唯一标识"); + if (!force) { + PluginInfoEntity byKey = pluginInfoService.getByKey(pluginJson.getKey()); + if (ObjUtil.isNotEmpty(byKey)) { + CoolPreconditions.returnData( + new CoolPreconditions.ReturnData(1, "插件已存在,继续安装将覆盖")); + } + } if (ObjUtil.isNotEmpty(pluginJson.getHook())) { // 查找hook是否已经存在,提示是否要替换,原hook将关闭 PluginInfoEntity pluginInfoEntity = pluginInfoService - .getPluginInfoEntityByHookNoJarFile(pluginJson.getHook()); + .getPluginInfoEntityByHook(pluginJson.getHook()); if (!force) { CoolPreconditions.returnData( ObjUtil.isNotEmpty(pluginInfoEntity) @@ -69,32 +117,15 @@ public class DynamicJarLoaderService { pluginJson.setSameHookId(pluginInfoEntity.getId()); } } - - // 加载类 - List> plugins = new ArrayList<>(); - try (JarFile jarFile = ((JarURLConnection) jarUrl.openConnection()).getJarFile()) { - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - loadClass(entries, dynamicJarClassLoader, plugins); - } - } - - // 校验插件 - checkPlugin(plugins); - registerPlugin(pluginJson.getKey(), plugins.get(0), dynamicJarClassLoader, force); - dynamicJarClassLoaderMap.put(pluginJson.getKey(), dynamicJarClassLoader); - - log.info("插件{}初始化成功.", pluginJson.getKey()); return pluginJson; } /** * 加载class */ - private static void loadClass(Enumeration entries, + private static void loadClass(JarEntry jarEntry, DynamicJarClassLoader dynamicJarClassLoader, List> plugins) throws ClassNotFoundException { - JarEntry jarEntry = entries.nextElement(); String entryName = jarEntry.getName(); if (!entryName.endsWith(".class")) { @@ -142,9 +173,10 @@ public class DynamicJarLoaderService { */ public boolean uninstall(String key) { DynamicJarClassLoader dynamicJarClassLoader = getDynamicJarClassLoader(key); - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); +// ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + log.info("插件{}开始卸载", key); try { - Thread.currentThread().setContextClassLoader(dynamicJarClassLoader); +// Thread.currentThread().setContextClassLoader(dynamicJarClassLoader); pluginMap.remove(key); if (dynamicJarClassLoader != null) { dynamicJarClassLoader.unload(); @@ -153,9 +185,9 @@ public class DynamicJarLoaderService { log.error("uninstall {}失败", key, e); CoolPreconditions.alwaysThrow("卸载失败"); } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); +// Thread.currentThread().setContextClassLoader(originalClassLoader); } - log.info("卸载插件{}", key); + log.info("插件{}卸载完成", key); return true; } diff --git a/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java b/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java index 7ca7b42..9642df5 100644 --- a/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java +++ b/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java @@ -9,7 +9,6 @@ import com.cool.core.annotation.IgnoreRecycleData; import com.cool.core.base.BaseController; import com.cool.core.plugin.service.CoolPluginService; import com.cool.core.request.R; -import com.cool.core.util.EntityUtils; import com.cool.modules.plugin.entity.PluginInfoEntity; import com.cool.modules.plugin.service.PluginInfoService; import com.mybatisflex.core.query.QueryWrapper; @@ -35,11 +34,8 @@ public class AdminPluginInfoController extends BaseController { @Column(typeHandler = Fastjson2TypeHandler.class) private PluginJson pluginJson; - @ColumnDefine(comment = "jar二进制文件", type = "longblob", notNull = true) + @ColumnDefine(comment = "jar二进制文件-废弃", type = "longblob") private byte[] jarFile; @ColumnDefine(comment = "配置", type = "json") diff --git a/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java b/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java index a8291d7..af97484 100644 --- a/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java +++ b/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java @@ -4,9 +4,7 @@ import com.cool.core.base.BaseService; import com.cool.modules.plugin.entity.PluginInfoEntity; public interface PluginInfoService extends BaseService { - PluginInfoEntity getByKeyNoJarFile(String key); + PluginInfoEntity getByKey(String key); - PluginInfoEntity getPluginInfoEntityByHookNoJarFile(String hook); - - PluginInfoEntity getPluginInfoEntityByIdNoJarFile(Long id); + PluginInfoEntity getPluginInfoEntityByHook(String hook); } diff --git a/src/main/java/com/cool/modules/plugin/service/impl/PluginInfoServiceImpl.java b/src/main/java/com/cool/modules/plugin/service/impl/PluginInfoServiceImpl.java index a18bcc8..6187fd5 100644 --- a/src/main/java/com/cool/modules/plugin/service/impl/PluginInfoServiceImpl.java +++ b/src/main/java/com/cool/modules/plugin/service/impl/PluginInfoServiceImpl.java @@ -1,15 +1,14 @@ package com.cool.modules.plugin.service.impl; +import static com.cool.modules.plugin.entity.table.PluginInfoEntityTableDef.PLUGIN_INFO_ENTITY; + import com.cool.core.base.BaseServiceImpl; -import com.cool.core.util.EntityUtils; import com.cool.modules.plugin.entity.PluginInfoEntity; import com.cool.modules.plugin.mapper.PluginInfoMapper; import com.cool.modules.plugin.service.PluginInfoService; import com.mybatisflex.core.query.QueryWrapper; import org.springframework.stereotype.Service; -import static com.cool.modules.plugin.entity.table.PluginInfoEntityTableDef.PLUGIN_INFO_ENTITY; - /** * 插件信息服务类 */ @@ -21,8 +20,8 @@ public class PluginInfoServiceImpl extends BaseServiceImpl