去掉插件二进制入库,打印插件加载耗时
This commit is contained in:
@@ -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,18 +32,14 @@ public class DynamicJarClassLoader extends URLClassLoader {
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) {
|
||||
// 从已加载的类集合中移除该类
|
||||
String className = entry.getKey();
|
||||
loadedClasses.remove(className);
|
||||
try {
|
||||
// 调用该类的destory方法,回收资源
|
||||
Class<?> clazz = entry.getValue();
|
||||
Method destory = clazz.getDeclaredMethod("destory");
|
||||
destory.invoke(clazz);
|
||||
} catch (NoClassDefFoundError | Exception ignored) {
|
||||
}
|
||||
}
|
||||
// 从其父类加载器的加载器层次结构中移除该类加载器
|
||||
close();
|
||||
@@ -52,4 +47,6 @@ public class DynamicJarClassLoader extends URLClassLoader {
|
||||
log.error("unload error", e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user