去掉插件二进制入库,打印插件加载耗时

This commit is contained in:
ruying408
2024-07-27 23:47:07 +08:00
parent c5c7f3015c
commit a8c0954a39
7 changed files with 101 additions and 90 deletions

View File

@@ -1,6 +1,5 @@
package com.cool.core.plugin.config; package com.cool.core.plugin.config;
import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Map; import java.util.Map;
@@ -33,18 +32,14 @@ public class DynamicJarClassLoader extends URLClassLoader {
} }
public void unload() { public void unload() {
new Thread(new Runnable() {
@Override
public void run() {
try { try {
for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) { for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) {
// 从已加载的类集合中移除该类 // 从已加载的类集合中移除该类
String className = entry.getKey(); String className = entry.getKey();
loadedClasses.remove(className); loadedClasses.remove(className);
try {
// 调用该类的destory方法回收资源
Class<?> clazz = entry.getValue();
Method destory = clazz.getDeclaredMethod("destory");
destory.invoke(clazz);
} catch (NoClassDefFoundError | Exception ignored) {
}
} }
// 从其父类加载器的加载器层次结构中移除该类加载器 // 从其父类加载器的加载器层次结构中移除该类加载器
close(); close();
@@ -52,4 +47,6 @@ public class DynamicJarClassLoader extends URLClassLoader {
log.error("unload error", e); log.error("unload error", e);
} }
} }
}).start();
}
} }

View File

@@ -67,8 +67,8 @@ public class CoolPluginService {
File file = new File(pluginJson.getJarPath()); File file = new File(pluginJson.getJarPath());
// 检查路径是否存在 // 检查路径是否存在
if (!file.exists()) { if (!file.exists()) {
PluginInfoEntity pluginInfoEntity = pluginInfoService.getById(entity.getId()); log.warn("插件不存在,请重新安装!");
FileUtil.writeBytes(pluginInfoEntity.getJarFile(), file); return;
} }
file = new File(pluginJson.getJarPath()); file = new File(pluginJson.getJarPath());
if (file.exists()) { if (file.exists()) {
@@ -100,32 +100,38 @@ public class CoolPluginService {
PluginJson pluginJson = dynamicJarLoaderService.install(jarFilePath, force); PluginJson pluginJson = dynamicJarLoaderService.install(jarFilePath, force);
key = pluginJson.getKey(); key = pluginJson.getKey();
// 保存插件信息入库 // 保存插件信息入库
savePluginInfo(pluginJson, jarFilePath, jarFile, force); savePluginInfo(pluginJson, jarFilePath, force);
// 把 ApplicationContext 对象传递打插件类中使其在插件中也能正常使用spring bean对象 // 把 ApplicationContext 对象传递打插件类中使其在插件中也能正常使用spring bean对象
CoolPluginInvokers.setApplicationContext(pluginJson.getKey()); CoolPluginInvokers.setApplicationContext(pluginJson.getKey());
} catch (PersistenceException persistenceException) { } catch (PersistenceException persistenceException) {
extractedAfterErr(jarFile, key);
if (persistenceException.getMessage().contains("Duplicate entry")) { if (persistenceException.getMessage().contains("Duplicate entry")) {
// 唯一键冲突 // 唯一键冲突
CoolPreconditions.returnData( CoolPreconditions.returnData(
new CoolPreconditions.ReturnData(1, "插件已存在,继续安装将覆盖")); new CoolPreconditions.ReturnData(1, "插件已存在,继续安装将覆盖"));
} }
if (ObjUtil.isNotEmpty(key)) {
// 报错失败,调用卸载
dynamicJarLoaderService.uninstall(key);
}
CoolPreconditions.alwaysThrow(persistenceException.getMessage()); CoolPreconditions.alwaysThrow(persistenceException.getMessage());
} catch (CoolException e) { } catch (CoolException e) {
FileUtil.del(jarFile); extractedAfterErr(jarFile, key);
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
FileUtil.del(jarFile);
log.error("插件安装失败", e); log.error("插件安装失败", e);
extractedAfterErr(jarFile, key);
CoolPreconditions.alwaysThrow("插件安装失败", e); CoolPreconditions.alwaysThrow("插件安装失败", e);
} finally { } finally {
Thread.currentThread().setContextClassLoader(originalClassLoader); Thread.currentThread().setContextClassLoader(originalClassLoader);
} }
} }
private void extractedAfterErr(File jarFile, String key) {
FileUtil.del(jarFile);
if (ObjUtil.isNotEmpty(key)) {
// 报错失败,调用卸载
dynamicJarLoaderService.uninstall(key);
}
}
/** /**
* 保存jar文件 * 保存jar文件
*/ */
@@ -153,7 +159,7 @@ public class CoolPluginService {
* 卸载 * 卸载
*/ */
public void uninstall(Long id) { public void uninstall(Long id) {
PluginInfoEntity pluginInfoEntity = pluginInfoService.getPluginInfoEntityByIdNoJarFile(id); PluginInfoEntity pluginInfoEntity = pluginInfoService.getById(id);
CoolPreconditions.checkEmpty(pluginInfoEntity, "插件不存在"); CoolPreconditions.checkEmpty(pluginInfoEntity, "插件不存在");
if (dynamicJarLoaderService.uninstall(pluginInfoEntity.getKey())) { if (dynamicJarLoaderService.uninstall(pluginInfoEntity.getKey())) {
boolean flag = pluginInfoEntity.removeById(); 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) { boolean force) {
CoolPreconditions.checkEmpty(pluginJson, "插件安装失败"); CoolPreconditions.checkEmpty(pluginJson, "插件安装失败");
pluginJson.setJarPath(jarFilePath); pluginJson.setJarPath(jarFilePath);
@@ -175,8 +181,6 @@ public class CoolPluginService {
setLogoOrReadme(pluginJson, pluginInfo); setLogoOrReadme(pluginJson, pluginInfo);
pluginInfo.setKey(pluginJson.getKey()); pluginInfo.setKey(pluginJson.getKey());
pluginInfo.setPluginJson(pluginJson); pluginInfo.setPluginJson(pluginJson);
// 转二进制
pluginInfo.setJarFile(FileUtil.readBytes(jarFile));
if (force) { if (force) {
// 判断是否有同名插件, 有将其关闭 // 判断是否有同名插件, 有将其关闭
closeSameNamePlugin(pluginJson); closeSameNamePlugin(pluginJson);
@@ -192,7 +196,7 @@ public class CoolPluginService {
*/ */
private void coverPlugin(PluginJson pluginJson, PluginInfoEntity pluginInfo) { private void coverPlugin(PluginJson pluginJson, PluginInfoEntity pluginInfo) {
// 通过key 找到id // 通过key 找到id
PluginInfoEntity one = pluginInfoService.getByKeyNoJarFile(pluginJson.getKey()); PluginInfoEntity one = pluginInfoService.getByKey(pluginJson.getKey());
if (ObjUtil.isNotEmpty(one)) { if (ObjUtil.isNotEmpty(one)) {
String oldJarPath = one.getPluginJson().getJarPath(); String oldJarPath = one.getPluginJson().getJarPath();
// 重新加载配置不更新 // 重新加载配置不更新
@@ -204,6 +208,7 @@ public class CoolPluginService {
// 忽略无变更,无需更新的字段 // 忽略无变更,无需更新的字段
ignoreNoChange(pluginInfo, one); ignoreNoChange(pluginInfo, one);
BeanUtil.copyProperties(pluginInfo, one, options); BeanUtil.copyProperties(pluginInfo, one, options);
one.setStatus(1);
if (one.updateById()) { if (one.updateById()) {
// 覆盖时删除旧版本插件 // 覆盖时删除旧版本插件
FileUtil.del(oldJarPath); FileUtil.del(oldJarPath);
@@ -263,7 +268,7 @@ public class CoolPluginService {
} }
public void updatePlugin(PluginInfoEntity entity) { public void updatePlugin(PluginInfoEntity entity) {
PluginInfoEntity dbPluginInfoEntity = pluginInfoService.getPluginInfoEntityByIdNoJarFile( PluginInfoEntity dbPluginInfoEntity = pluginInfoService.getById(
entity.getId()); entity.getId());
// 调用插件更新配置标识 // 调用插件更新配置标识
boolean invokePluginConfig = false; boolean invokePluginConfig = false;
@@ -295,7 +300,7 @@ public class CoolPluginService {
if (ObjUtil.isNotEmpty(dbPluginInfoEntity.getHook())) { if (ObjUtil.isNotEmpty(dbPluginInfoEntity.getHook())) {
// 查找是否有同名hook有同名hook,如果状态为开启不允许在开启,需先关闭原来 // 查找是否有同名hook有同名hook,如果状态为开启不允许在开启,需先关闭原来
PluginInfoEntity hookPlugin = pluginInfoService PluginInfoEntity hookPlugin = pluginInfoService
.getPluginInfoEntityByHookNoJarFile(dbPluginInfoEntity.getHook()); .getPluginInfoEntityByHook(dbPluginInfoEntity.getHook());
if (ObjUtil.isNotEmpty(hookPlugin)) { if (ObjUtil.isNotEmpty(hookPlugin)) {
CoolPreconditions.check( CoolPreconditions.check(
!ObjUtil.equals(hookPlugin.getKey(), dbPluginInfoEntity.getKey()) !ObjUtil.equals(hookPlugin.getKey(), dbPluginInfoEntity.getKey())
@@ -317,7 +322,7 @@ public class CoolPluginService {
* 通过hook获取插件 * 通过hook获取插件
*/ */
public PluginInfoEntity getPluginInfoEntityByHook(String hook) { public PluginInfoEntity getPluginInfoEntityByHook(String hook) {
return pluginInfoService.getPluginInfoEntityByHookNoJarFile(hook); return pluginInfoService.getPluginInfoEntityByHook(hook);
} }
/** /**

View File

@@ -16,8 +16,9 @@ import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.JarURLConnection; import java.net.JarURLConnection;
import java.net.URL; import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -46,15 +47,62 @@ public class DynamicJarLoaderService {
DynamicJarClassLoader dynamicJarClassLoader = new DynamicJarClassLoader(new URL[]{jarUrl}, DynamicJarClassLoader dynamicJarClassLoader = new DynamicJarClassLoader(new URL[]{jarUrl},
Thread.currentThread().getContextClassLoader()); Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(dynamicJarClassLoader); Thread.currentThread().setContextClassLoader(dynamicJarClassLoader);
PluginJson pluginJson = getPluginJsonAndCheck(force, dynamicJarClassLoader);
// 加载类
List<Class<?>> 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<JarEntry> 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"); InputStream inputStream = dynamicJarClassLoader.getResourceAsStream("plugin.json");
CoolPreconditions.check(ObjUtil.isEmpty(inputStream), "不合规插件未找到plugin.json文件"); CoolPreconditions.check(ObjUtil.isEmpty(inputStream), "不合规插件未找到plugin.json文件");
String pluginJsonStr = StrUtil.str(IoUtil.readBytes(inputStream), "UTF-8"); String pluginJsonStr = StrUtil.str(IoUtil.readBytes(inputStream), "UTF-8");
PluginJson pluginJson = JSONUtil.toBean(pluginJsonStr, PluginJson.class); PluginJson pluginJson = JSONUtil.toBean(pluginJsonStr, PluginJson.class);
CoolPreconditions.check(ObjUtil.isEmpty(pluginJson.getKey()), "该插件缺少唯一标识"); 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())) { if (ObjUtil.isNotEmpty(pluginJson.getHook())) {
// 查找hook是否已经存在,提示是否要替换原hook将关闭 // 查找hook是否已经存在,提示是否要替换原hook将关闭
PluginInfoEntity pluginInfoEntity = pluginInfoService PluginInfoEntity pluginInfoEntity = pluginInfoService
.getPluginInfoEntityByHookNoJarFile(pluginJson.getHook()); .getPluginInfoEntityByHook(pluginJson.getHook());
if (!force) { if (!force) {
CoolPreconditions.returnData( CoolPreconditions.returnData(
ObjUtil.isNotEmpty(pluginInfoEntity) ObjUtil.isNotEmpty(pluginInfoEntity)
@@ -69,32 +117,15 @@ public class DynamicJarLoaderService {
pluginJson.setSameHookId(pluginInfoEntity.getId()); pluginJson.setSameHookId(pluginInfoEntity.getId());
} }
} }
// 加载类
List<Class<?>> plugins = new ArrayList<>();
try (JarFile jarFile = ((JarURLConnection) jarUrl.openConnection()).getJarFile()) {
Enumeration<JarEntry> 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; return pluginJson;
} }
/** /**
* 加载class * 加载class
*/ */
private static void loadClass(Enumeration<JarEntry> entries, private static void loadClass(JarEntry jarEntry,
DynamicJarClassLoader dynamicJarClassLoader, List<Class<?>> plugins) DynamicJarClassLoader dynamicJarClassLoader, List<Class<?>> plugins)
throws ClassNotFoundException { throws ClassNotFoundException {
JarEntry jarEntry = entries.nextElement();
String entryName = jarEntry.getName(); String entryName = jarEntry.getName();
if (!entryName.endsWith(".class")) { if (!entryName.endsWith(".class")) {
@@ -142,9 +173,10 @@ public class DynamicJarLoaderService {
*/ */
public boolean uninstall(String key) { public boolean uninstall(String key) {
DynamicJarClassLoader dynamicJarClassLoader = getDynamicJarClassLoader(key); DynamicJarClassLoader dynamicJarClassLoader = getDynamicJarClassLoader(key);
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); // ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
log.info("插件{}开始卸载", key);
try { try {
Thread.currentThread().setContextClassLoader(dynamicJarClassLoader); // Thread.currentThread().setContextClassLoader(dynamicJarClassLoader);
pluginMap.remove(key); pluginMap.remove(key);
if (dynamicJarClassLoader != null) { if (dynamicJarClassLoader != null) {
dynamicJarClassLoader.unload(); dynamicJarClassLoader.unload();
@@ -153,9 +185,9 @@ public class DynamicJarLoaderService {
log.error("uninstall {}失败", key, e); log.error("uninstall {}失败", key, e);
CoolPreconditions.alwaysThrow("卸载失败"); CoolPreconditions.alwaysThrow("卸载失败");
} finally { } finally {
Thread.currentThread().setContextClassLoader(originalClassLoader); // Thread.currentThread().setContextClassLoader(originalClassLoader);
} }
log.info("卸载插件{}", key); log.info("插件{}卸载完成", key);
return true; return true;
} }

View File

@@ -9,7 +9,6 @@ import com.cool.core.annotation.IgnoreRecycleData;
import com.cool.core.base.BaseController; import com.cool.core.base.BaseController;
import com.cool.core.plugin.service.CoolPluginService; import com.cool.core.plugin.service.CoolPluginService;
import com.cool.core.request.R; import com.cool.core.request.R;
import com.cool.core.util.EntityUtils;
import com.cool.modules.plugin.entity.PluginInfoEntity; import com.cool.modules.plugin.entity.PluginInfoEntity;
import com.cool.modules.plugin.service.PluginInfoService; import com.cool.modules.plugin.service.PluginInfoService;
import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.query.QueryWrapper;
@@ -35,11 +34,8 @@ public class AdminPluginInfoController extends BaseController<PluginInfoService,
@Override @Override
protected void init(HttpServletRequest request, JSONObject requestParams) { protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(createOp().queryWrapper( setPageOption(createOp().queryWrapper(
QueryWrapper.create().orderBy(PLUGIN_INFO_ENTITY.UPDATE_TIME, false)) QueryWrapper.create().orderBy(PLUGIN_INFO_ENTITY.UPDATE_TIME, false)));
.select(EntityUtils.getFieldNamesWithSuperClass(PLUGIN_INFO_ENTITY.DEFAULT_COLUMNS,
PLUGIN_INFO_ENTITY.JAR_FILE.getName())));
} }
@Override @Override

View File

@@ -52,7 +52,7 @@ public class PluginInfoEntity extends BaseEntity<PluginInfoEntity> {
@Column(typeHandler = Fastjson2TypeHandler.class) @Column(typeHandler = Fastjson2TypeHandler.class)
private PluginJson pluginJson; private PluginJson pluginJson;
@ColumnDefine(comment = "jar二进制文件", type = "longblob", notNull = true) @ColumnDefine(comment = "jar二进制文件-废弃", type = "longblob")
private byte[] jarFile; private byte[] jarFile;
@ColumnDefine(comment = "配置", type = "json") @ColumnDefine(comment = "配置", type = "json")

View File

@@ -4,9 +4,7 @@ import com.cool.core.base.BaseService;
import com.cool.modules.plugin.entity.PluginInfoEntity; import com.cool.modules.plugin.entity.PluginInfoEntity;
public interface PluginInfoService extends BaseService<PluginInfoEntity> { public interface PluginInfoService extends BaseService<PluginInfoEntity> {
PluginInfoEntity getByKeyNoJarFile(String key); PluginInfoEntity getByKey(String key);
PluginInfoEntity getPluginInfoEntityByHookNoJarFile(String hook); PluginInfoEntity getPluginInfoEntityByHook(String hook);
PluginInfoEntity getPluginInfoEntityByIdNoJarFile(Long id);
} }

View File

@@ -1,15 +1,14 @@
package com.cool.modules.plugin.service.impl; 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.base.BaseServiceImpl;
import com.cool.core.util.EntityUtils;
import com.cool.modules.plugin.entity.PluginInfoEntity; import com.cool.modules.plugin.entity.PluginInfoEntity;
import com.cool.modules.plugin.mapper.PluginInfoMapper; import com.cool.modules.plugin.mapper.PluginInfoMapper;
import com.cool.modules.plugin.service.PluginInfoService; import com.cool.modules.plugin.service.PluginInfoService;
import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service; 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<PluginInfoMapper, Plu
* 通过key获取插件信息,不带jar二进制 * 通过key获取插件信息,不带jar二进制
*/ */
@Override @Override
public PluginInfoEntity getByKeyNoJarFile(String key) { public PluginInfoEntity getByKey(String key) {
QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper(); QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.and(PLUGIN_INFO_ENTITY.KEY.eq(key)); queryWrapper.and(PLUGIN_INFO_ENTITY.KEY.eq(key));
return getOne(queryWrapper); return getOne(queryWrapper);
} }
@@ -31,25 +30,9 @@ public class PluginInfoServiceImpl extends BaseServiceImpl<PluginInfoMapper, Plu
* 通过hook获取插件信息,不带jar二进制 * 通过hook获取插件信息,不带jar二进制
*/ */
@Override @Override
public PluginInfoEntity getPluginInfoEntityByHookNoJarFile(String hook) { public PluginInfoEntity getPluginInfoEntityByHook(String hook) {
QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper().and(PLUGIN_INFO_ENTITY.HOOK.eq(hook)) QueryWrapper queryWrapper = QueryWrapper.create().and(PLUGIN_INFO_ENTITY.HOOK.eq(hook))
.and(PLUGIN_INFO_ENTITY.STATUS.eq(1)).limit(1); .and(PLUGIN_INFO_ENTITY.STATUS.eq(1)).limit(1);
return getOne(queryWrapper); return getOne(queryWrapper);
} }
/**
* 通过id获取插件信息,不带jar二进制
*/
@Override
public PluginInfoEntity getPluginInfoEntityByIdNoJarFile(Long id) {
QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper().and(PLUGIN_INFO_ENTITY.ID.eq(id));
return getOne(queryWrapper);
}
/**
* 获取查询对象,排除掉 jar二进制
*/
private QueryWrapper getPluginInfoEntityQueryWrapper() {
return QueryWrapper.create().select(EntityUtils.getFieldNamesWithSuperClass(PLUGIN_INFO_ENTITY.DEFAULT_COLUMNS, PLUGIN_INFO_ENTITY.JAR_FILE.getName()));
}
} }