From c77be231fae34ec4ec196da910723b3fdd0a2c33 Mon Sep 17 00:00:00 2001 From: ruying408 <1877972603@qq.com> Date: Sun, 28 Jul 2024 13:48:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=BE=9D=E8=B5=96=E7=B1=BB?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=BC=82=E6=AD=A5=E6=96=B9=E5=BC=8F=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=EF=BC=8C=E8=A7=A3=E5=86=B3=E4=B8=BB=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=85=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/config/DynamicJarClassLoader.java | 82 ++++++++++++ .../plugin/service/CoolPluginService.java | 38 ++++-- .../service/DynamicJarLoaderService.java | 118 ++++++------------ .../com/cool/core/util/AnnotationUtils.java | 10 +- .../admin/AdminPluginInfoController.java | 6 +- .../plugin/entity/PluginInfoEntity.java | 2 +- .../plugin/service/PluginInfoService.java | 2 + .../service/impl/PluginInfoServiceImpl.java | 19 ++- src/main/resources/application.yml | 7 +- 9 files changed, 182 insertions(+), 102 deletions(-) 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 bf01d84..18a28b5 100644 --- a/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java +++ b/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java @@ -1,9 +1,16 @@ package com.cool.core.plugin.config; +import com.cool.core.exception.CoolPreconditions; +import com.cool.core.util.AnnotationUtils; +import com.cool.core.util.CompilerUtils; import java.net.URL; import java.net.URLClassLoader; +import java.time.Duration; +import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarEntry; import lombok.extern.slf4j.Slf4j; /** @@ -18,6 +25,8 @@ public class DynamicJarClassLoader extends URLClassLoader { super(urls, parent); } + private Boolean lock = false; + @Override protected Class findClass(String name) throws ClassNotFoundException { // 从已加载的类集合中获取指定名称的类 @@ -32,6 +41,8 @@ public class DynamicJarClassLoader extends URLClassLoader { } public void unload() { + CoolPreconditions.check(lock, "异步加载任务还未完成,请稍后重试......"); + lock = true; new Thread(new Runnable() { @Override public void run() { @@ -45,6 +56,77 @@ public class DynamicJarClassLoader extends URLClassLoader { close(); } catch (Exception e) { log.error("unload error", e); + } finally{ + lock = false; + } + } + }).start(); + } + public void loadClass(List jarEntries, List> plugins) { + for (JarEntry jarEntry : jarEntries) { + loadClass(jarEntry, plugins); + } + } + /** + * 加载class + */ + public void loadClass(JarEntry jarEntry, List> plugins) { + String entryName = jarEntry.getName(); + String className = entryName.replace('/', '.').substring(0, entryName.length() - 6); + if (entryName.startsWith(CompilerUtils.META_INF_VERSIONS)) { + // 处理多版本类 + String jdkVersion = CompilerUtils.getJdkVersion(); + if (!entryName.startsWith(CompilerUtils.META_INF_VERSIONS + jdkVersion)) { + return; + } + // 替换版本目录 + className = className.replace((CompilerUtils.META_INF_VERSIONS + jdkVersion).replace("/", ".") + ".", ""); + } + try { + // 加载类 + Class clazz = super.loadClass(className); + if (plugins != null && AnnotationUtils.hasCoolPluginAnnotation(clazz)) { + // 添加插件 + plugins.add(clazz); + } + } catch (ClassNotFoundException e) { + log.error("loadClassErr", e); + } catch ( NoClassDefFoundError | UnsupportedClassVersionError ignored) { + } + } + + /** + * 异步加载 + */ + public void asyncLoadClass(List list) { + CoolPreconditions.check(lock, "异步加载任务还未完成,请稍后重试......"); + lock = true; + new Thread(new Runnable() { + @Override + public void run() { + log.info("开始异步加载...."); + Instant start = Instant.now(); + int size = list.size(); + int currentProgress = 0; + int progressThreshold = 10; // 输出进度的阈值为10% + int count = 0; + try{ + for (JarEntry jarEntry : list) { + count++; + loadClass(jarEntry, null); + // 计算进度百分比 + 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()); + lock = false; } } }).start(); 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 6c7766f..3c06943 100644 --- a/src/main/java/com/cool/core/plugin/service/CoolPluginService.java +++ b/src/main/java/com/cool/core/plugin/service/CoolPluginService.java @@ -5,6 +5,7 @@ import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.codec.Base64Encoder; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.cool.core.config.PluginJson; @@ -43,9 +44,12 @@ public class CoolPluginService { final private PluginInfoService pluginInfoService; - @Value("${cool.pluginPath}") + @Value("${cool.plugin.path}") private String pluginPath; + @Value("${cool.plugin.toDb:false}") + private Boolean toDb; + public void init() { List list = pluginInfoService .list(QueryWrapper @@ -65,10 +69,14 @@ public class CoolPluginService { private void initInstall(PluginInfoEntity entity) { PluginJson pluginJson = entity.getPluginJson(); File file = new File(pluginJson.getJarPath()); - // 检查路径是否存在 + // 检查文件是否存在 if (!file.exists()) { - log.warn("插件不存在,请重新安装!"); - return; + PluginInfoEntity pluginInfoEntity = pluginInfoService.getById(entity.getId()); + if (ObjUtil.isEmpty(pluginInfoEntity.getJarFile())) { + log.warn("插件文件不存在,请重新安装!"); + return; + } + FileUtil.writeBytes(pluginInfoEntity.getJarFile(), file); } file = new File(pluginJson.getJarPath()); if (file.exists()) { @@ -100,7 +108,7 @@ public class CoolPluginService { PluginJson pluginJson = dynamicJarLoaderService.install(jarFilePath, force); key = pluginJson.getKey(); // 保存插件信息入库 - savePluginInfo(pluginJson, jarFilePath, force); + savePluginInfo(pluginJson, jarFilePath, jarFile, force); // 把 ApplicationContext 对象传递打插件类中,使其在插件中也能正常使用spring bean对象 CoolPluginInvokers.setApplicationContext(pluginJson.getKey()); } catch (PersistenceException persistenceException) { @@ -159,13 +167,12 @@ public class CoolPluginService { * 卸载 */ public void uninstall(Long id) { - PluginInfoEntity pluginInfoEntity = pluginInfoService.getById(id); + PluginInfoEntity pluginInfoEntity = pluginInfoService.getPluginInfoEntityById(id); CoolPreconditions.checkEmpty(pluginInfoEntity, "插件不存在"); - if (dynamicJarLoaderService.uninstall(pluginInfoEntity.getKey())) { - boolean flag = pluginInfoEntity.removeById(); - if (flag) { - FileUtil.del(pluginInfoEntity.getPluginJson().getJarPath()); - } + dynamicJarLoaderService.uninstall(pluginInfoEntity.getKey()); + boolean flag = pluginInfoEntity.removeById(); + if (flag) { + FileUtil.del(pluginInfoEntity.getPluginJson().getJarPath()); } } @@ -173,6 +180,7 @@ public class CoolPluginService { * 保存插件信息 */ private void savePluginInfo(PluginJson pluginJson, String jarFilePath , + File jarFile, boolean force) { CoolPreconditions.checkEmpty(pluginJson, "插件安装失败"); pluginJson.setJarPath(jarFilePath); @@ -181,6 +189,10 @@ public class CoolPluginService { setLogoOrReadme(pluginJson, pluginInfo); pluginInfo.setKey(pluginJson.getKey()); pluginInfo.setPluginJson(pluginJson); + if (BooleanUtil.isTrue(toDb)) { + // 转二进制 + pluginInfo.setJarFile(FileUtil.readBytes(jarFile)); + } if (force) { // 判断是否有同名插件, 有将其关闭 closeSameNamePlugin(pluginJson); @@ -213,6 +225,8 @@ public class CoolPluginService { // 覆盖时删除旧版本插件 FileUtil.del(oldJarPath); } + } else { + pluginInfo.save(); } } @@ -268,7 +282,7 @@ public class CoolPluginService { } public void updatePlugin(PluginInfoEntity entity) { - PluginInfoEntity dbPluginInfoEntity = pluginInfoService.getById( + PluginInfoEntity dbPluginInfoEntity = pluginInfoService.getPluginInfoEntityById( entity.getId()); // 调用插件更新配置标识 boolean invokePluginConfig = false; 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 4d62b43..0c66622 100644 --- a/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java +++ b/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java @@ -8,16 +8,13 @@ import cn.hutool.json.JSONUtil; import com.cool.core.config.PluginJson; import com.cool.core.exception.CoolPreconditions; import com.cool.core.plugin.config.DynamicJarClassLoader; -import com.cool.core.util.AnnotationUtils; -import com.cool.core.util.CompilerUtils; import com.cool.modules.plugin.entity.PluginInfoEntity; import com.cool.modules.plugin.service.PluginInfoService; import java.io.File; +import java.io.IOException; 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.List; import java.util.Map; @@ -48,41 +45,44 @@ public class DynamicJarLoaderService { 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()); - } + // 加载class + List> plugins = loadClass(jarUrl, dynamicJarClassLoader); // 校验插件 checkPlugin(plugins); + // 注册插件,目前一个插件包,只允许有一个插件入口 registerPlugin(pluginJson.getKey(), plugins.get(0), dynamicJarClassLoader, force); - dynamicJarClassLoaderMap.put(pluginJson.getKey(), dynamicJarClassLoader); - - log.info("插件{}安装成功.", pluginJson.getKey()); return pluginJson; } + /** + * 加载class + */ + private static List> loadClass(URL jarUrl, + DynamicJarClassLoader dynamicJarClassLoader) throws IOException { + // 加载类 + List> plugins = new ArrayList<>(); + try (JarFile jarFile = ((JarURLConnection) jarUrl.openConnection()).getJarFile()) { + List list = jarFile.stream().filter(o -> o.getName().endsWith(".class")).toList(); + List firstNElements = getFirstNElements(list, 100); + // 先加载前100个类,主够包含了插件主类,其余的异步加载 + dynamicJarClassLoader.loadClass(firstNElements, plugins); + // 异步加载插件依赖类 + dynamicJarClassLoader.asyncLoadClass(list); + } + return plugins; + } + + public static List getFirstNElements(List list, int n) { + // 确保 n 不超过 list 的大小 + if (list.size() <= n) { + return new ArrayList<>(list); // 返回原列表的副本 + } else { + return new ArrayList<>(list.subList(0, n)); // 返回前 N 个元素的副本 + } + } + /** * 校验,获取插件配置 */ @@ -120,37 +120,6 @@ public class DynamicJarLoaderService { return pluginJson; } - /** - * 加载class - */ - private static void loadClass(JarEntry jarEntry, - DynamicJarClassLoader dynamicJarClassLoader, List> plugins) - throws ClassNotFoundException { - - String entryName = jarEntry.getName(); - if (!entryName.endsWith(".class")) { - return; - } - String className = entryName.replace('/', '.').substring(0, entryName.length() - 6); - if (entryName.startsWith(CompilerUtils.META_INF_VERSIONS)) { - // 处理多版本类 - String jdkVersion = CompilerUtils.getJdkVersion(); - if (!entryName.startsWith(CompilerUtils.META_INF_VERSIONS + jdkVersion)) { - return; - } - // 替换版本目录 - className = className.replace((CompilerUtils.META_INF_VERSIONS + jdkVersion).replace("/", ".") + ".", ""); - } - try { - // 加载类 - Class clazz = dynamicJarClassLoader.loadClass(className); - if (AnnotationUtils.hasCoolPluginAnnotation(clazz)) { - plugins.add(clazz); - } - } catch (NoClassDefFoundError | UnsupportedClassVersionError ignored) { - } - } - /** * 注册插件 */ @@ -165,30 +134,23 @@ public class DynamicJarLoaderService { if (ObjUtil.isNotEmpty(key)) { pluginMap.remove(key); pluginMap.put(key, ReflectUtil.newInstance(pluginClazz)); + dynamicJarClassLoaderMap.put(key, dynamicJarClassLoader); } + log.info("插件{}注册成功.", key); } /** * 卸载 */ - public boolean uninstall(String key) { + public void uninstall(String key) { DynamicJarClassLoader dynamicJarClassLoader = getDynamicJarClassLoader(key); -// ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - log.info("插件{}开始卸载", key); - try { -// Thread.currentThread().setContextClassLoader(dynamicJarClassLoader); - pluginMap.remove(key); - if (dynamicJarClassLoader != null) { - dynamicJarClassLoader.unload(); - } - } catch (Exception e) { - log.error("uninstall {}失败", key, e); - CoolPreconditions.alwaysThrow("卸载失败"); - } finally { -// Thread.currentThread().setContextClassLoader(originalClassLoader); + if (dynamicJarClassLoader == null) { + return; } + log.info("插件{}开始卸载", key); + pluginMap.remove(key); + dynamicJarClassLoader.unload(); log.info("插件{}卸载完成", key); - return true; } /** diff --git a/src/main/java/com/cool/core/util/AnnotationUtils.java b/src/main/java/com/cool/core/util/AnnotationUtils.java index 2ab2c84..b65d6a3 100644 --- a/src/main/java/com/cool/core/util/AnnotationUtils.java +++ b/src/main/java/com/cool/core/util/AnnotationUtils.java @@ -1,6 +1,8 @@ package com.cool.core.util; import com.cool.core.annotation.CoolPlugin; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; @@ -8,9 +10,6 @@ import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; -import java.lang.annotation.Annotation; -import java.lang.reflect.Modifier; - @Slf4j public class AnnotationUtils { @@ -61,16 +60,13 @@ public class AnnotationUtils { return false; } try { -// if (clazz.getAnnotation(CoolPlugin.class) != null) { -// return true; -// } ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if (clazz.getAnnotation( (Class) contextClassLoader.loadClass(CoolPlugin.class.getName())) != null) { return true; } } catch (Exception e) { - log.error("出现异常:{}", e.getMessage()); + log.error("出现异常:{}", e.getMessage(), e); } return false; } 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 9642df5..7ca7b42 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,6 +9,7 @@ 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; @@ -34,8 +35,11 @@ public class AdminPluginInfoController extends BaseController { @Column(typeHandler = Fastjson2TypeHandler.class) private PluginJson pluginJson; - @ColumnDefine(comment = "jar二进制文件-废弃", type = "longblob") + @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 af97484..acea573 100644 --- a/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java +++ b/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java @@ -7,4 +7,6 @@ public interface PluginInfoService extends BaseService { PluginInfoEntity getByKey(String key); PluginInfoEntity getPluginInfoEntityByHook(String hook); + + PluginInfoEntity getPluginInfoEntityById(Long id); } 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 6187fd5..e213845 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 @@ -3,6 +3,7 @@ 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; @@ -31,8 +32,24 @@ public class PluginInfoServiceImpl extends BaseServiceImpl