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

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;
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<String, Class<?>> 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<String, Class<?>> 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();
}
}

View File

@@ -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);
}
/**

View File

@@ -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<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");
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<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;
}
/**
* 加载class
*/
private static void loadClass(Enumeration<JarEntry> entries,
private static void loadClass(JarEntry jarEntry,
DynamicJarClassLoader dynamicJarClassLoader, List<Class<?>> 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;
}

View File

@@ -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<PluginInfoService,
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(createOp().queryWrapper(
QueryWrapper.create().orderBy(PLUGIN_INFO_ENTITY.UPDATE_TIME, false))
.select(EntityUtils.getFieldNamesWithSuperClass(PLUGIN_INFO_ENTITY.DEFAULT_COLUMNS,
PLUGIN_INFO_ENTITY.JAR_FILE.getName())));
QueryWrapper.create().orderBy(PLUGIN_INFO_ENTITY.UPDATE_TIME, false)));
}
@Override

View File

@@ -52,7 +52,7 @@ public class PluginInfoEntity extends BaseEntity<PluginInfoEntity> {
@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")

View File

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

View File

@@ -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<PluginInfoMapper, Plu
* 通过key获取插件信息,不带jar二进制
*/
@Override
public PluginInfoEntity getByKeyNoJarFile(String key) {
QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper();
public PluginInfoEntity getByKey(String key) {
QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.and(PLUGIN_INFO_ENTITY.KEY.eq(key));
return getOne(queryWrapper);
}
@@ -31,25 +30,9 @@ public class PluginInfoServiceImpl extends BaseServiceImpl<PluginInfoMapper, Plu
* 通过hook获取插件信息,不带jar二进制
*/
@Override
public PluginInfoEntity getPluginInfoEntityByHookNoJarFile(String hook) {
QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper().and(PLUGIN_INFO_ENTITY.HOOK.eq(hook))
public PluginInfoEntity getPluginInfoEntityByHook(String hook) {
QueryWrapper queryWrapper = QueryWrapper.create().and(PLUGIN_INFO_ENTITY.HOOK.eq(hook))
.and(PLUGIN_INFO_ENTITY.STATUS.eq(1)).limit(1);
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()));
}
}