feat(插件管理): 优化插件加载机制
This commit is contained in:
parent
54a2147f8d
commit
b4f5f710db
|
@ -12,7 +12,9 @@ import io.metersphere.controller.request.PluginRequest;
|
|||
import io.metersphere.controller.request.PluginResourceDTO;
|
||||
import io.metersphere.plugin.core.ui.PluginResource;
|
||||
import io.metersphere.service.utils.CommonUtil;
|
||||
import io.metersphere.service.utils.MsClassLoader;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.Predicate;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -23,15 +25,18 @@ import java.io.File;
|
|||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class PluginService {
|
||||
@Resource
|
||||
private PluginMapper pluginMapper;
|
||||
@Resource
|
||||
private MsClassLoader classLoader;
|
||||
|
||||
public String editPlugin(MultipartFile file) {
|
||||
String id = UUID.randomUUID().toString();
|
||||
|
@ -80,6 +85,7 @@ public class PluginService {
|
|||
private List<PluginResourceDTO> getMethod(String path, String fileName) {
|
||||
List<PluginResourceDTO> resources = new LinkedList<>();
|
||||
try {
|
||||
// classLoader.unloadJarFile(path);
|
||||
this.loadJar(path);
|
||||
List<Class<?>> classes = CommonUtil.getSubClass(fileName);
|
||||
for (Class<?> aClass : classes) {
|
||||
|
@ -134,7 +140,10 @@ public class PluginService {
|
|||
method.setAccessible(true);
|
||||
// 获取系统类加载器
|
||||
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
|
||||
|
||||
URL url = jarFile.toURI().toURL();
|
||||
//URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
|
||||
|
||||
method.invoke(classLoader, url);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -142,13 +151,22 @@ public class PluginService {
|
|||
}
|
||||
}
|
||||
|
||||
private <T> Predicate<T> distinctByName(Function<? super T, ?> keys) {
|
||||
Map<Object, Boolean> sen = new ConcurrentHashMap<>();
|
||||
return t -> sen.putIfAbsent(keys.apply(t), Boolean.TRUE) == null;
|
||||
}
|
||||
|
||||
public void loadPlugins() {
|
||||
PluginExample example = new PluginExample();
|
||||
List<Plugin> plugins = pluginMapper.selectByExample(example);
|
||||
if (CollectionUtils.isNotEmpty(plugins)) {
|
||||
plugins.forEach(item -> {
|
||||
this.loadJar(item.getSourcePath());
|
||||
});
|
||||
plugins = plugins.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(()
|
||||
-> new TreeSet<>(Comparator.comparing(Plugin::getPluginId))), ArrayList::new));
|
||||
if (CollectionUtils.isNotEmpty(plugins)) {
|
||||
plugins.forEach(item -> {
|
||||
this.loadJar(item.getSourcePath());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package io.metersphere.service.utils;
|
||||
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class MsClassLoader {
|
||||
|
||||
private final static ConcurrentHashMap<String, MsURLClassLoader> LOADER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public MsURLClassLoader loadJar(String path) {
|
||||
try {
|
||||
String jarName = path.substring(path.indexOf("_") + 1);
|
||||
MsURLClassLoader urlClassLoader = LOADER_CACHE.get(jarName);
|
||||
if (urlClassLoader != null) {
|
||||
return urlClassLoader;
|
||||
}
|
||||
urlClassLoader = new MsURLClassLoader();
|
||||
File jarFile = new File(path);
|
||||
URL jarUrl = jarFile.toURI().toURL();
|
||||
urlClassLoader.addURLFile(jarUrl);
|
||||
LOADER_CACHE.put(jarName, urlClassLoader);
|
||||
return urlClassLoader;
|
||||
} catch (Exception ex) {
|
||||
LogUtil.error("加载JAR包失败:" + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Class loadClass(String jarName, String name) throws ClassNotFoundException {
|
||||
MsURLClassLoader urlClassLoader = LOADER_CACHE.get(jarName);
|
||||
if (urlClassLoader == null) {
|
||||
return null;
|
||||
}
|
||||
return urlClassLoader.loadClass(name);
|
||||
}
|
||||
|
||||
public void unloadJarFile(String path) throws MalformedURLException {
|
||||
String jarName = path.substring(path.indexOf("_") + 1);
|
||||
MsURLClassLoader urlClassLoader = LOADER_CACHE.get(jarName);
|
||||
if (urlClassLoader == null) {
|
||||
return;
|
||||
}
|
||||
urlClassLoader.unloadJarFile(path);
|
||||
urlClassLoader = null;
|
||||
LOADER_CACHE.remove(jarName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package io.metersphere.service.utils;
|
||||
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class MsURLClassLoader extends URLClassLoader {
|
||||
private JarURLConnection cachedJarFile = null;
|
||||
|
||||
public MsURLClassLoader() {
|
||||
super(new URL[]{}, findParentClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的文件url添加到类加载器的classpath中去,并缓存jar connection,方便以后卸载jar
|
||||
* 一个可想类加载器的classpath中添加的文件url
|
||||
*
|
||||
* @param
|
||||
*/
|
||||
public void addURLFile(URL file) {
|
||||
try {
|
||||
// 打开并缓存文件url连接
|
||||
URLConnection uc = file.openConnection();
|
||||
if (uc instanceof JarURLConnection) {
|
||||
uc.setUseCaches(true);
|
||||
((JarURLConnection) uc).getManifest();
|
||||
cachedJarFile = (JarURLConnection) uc;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to cache plugin JAR file: " + file.toExternalForm());
|
||||
}
|
||||
addURL(file);
|
||||
}
|
||||
|
||||
|
||||
public void unloadJarFile(String url) {
|
||||
JarURLConnection jarURLConnection = cachedJarFile;
|
||||
if (jarURLConnection == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
System.err.println("Unloading plugin JAR file " + jarURLConnection.getJarFile().getName());
|
||||
jarURLConnection.getJarFile().close();
|
||||
jarURLConnection = null;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to unload JAR file\n" + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 定位基于当前上下文的父类加载器
|
||||
*
|
||||
* @return 返回可用的父类加载器.
|
||||
*/
|
||||
private static ClassLoader findParentClassLoader() {
|
||||
ClassLoader parent = ClassLoader.getSystemClassLoader();
|
||||
if (parent == null) {
|
||||
parent = MsURLClassLoader.class.getClassLoader();
|
||||
}
|
||||
if (parent == null) {
|
||||
parent = ClassLoader.getSystemClassLoader();
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
}
|
|
@ -58,6 +58,18 @@ export default {
|
|||
this.$emit('runRefresh', data);
|
||||
}
|
||||
},
|
||||
sort(stepArray) {
|
||||
if (stepArray) {
|
||||
for (let i in stepArray) {
|
||||
if (!stepArray[i].clazzName) {
|
||||
stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type);
|
||||
}
|
||||
if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) {
|
||||
this.sort(stepArray[i].hashTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
run() {
|
||||
let projectId = getCurrentProjectID();
|
||||
// 如果envMap不存在,是单接口调用
|
||||
|
@ -83,6 +95,7 @@ export default {
|
|||
}
|
||||
threadGroup.hashTree.push(item);
|
||||
})
|
||||
this.sort(testPlan.hashTree);
|
||||
let reqObj = {id: this.reportId, testElement: testPlan, type: this.type, clazzName: this.clazzName ? this.clazzName : TYPE_TO_C.get(this.type), projectId: projectId, environmentMap: strMapToObj(this.envMap)};
|
||||
let bodyFiles = getBodyUploadFiles(reqObj, this.runData);
|
||||
if (this.runData[0].url) {
|
||||
|
|
Loading…
Reference in New Issue