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.controller.request.PluginResourceDTO;
|
||||||
import io.metersphere.plugin.core.ui.PluginResource;
|
import io.metersphere.plugin.core.ui.PluginResource;
|
||||||
import io.metersphere.service.utils.CommonUtil;
|
import io.metersphere.service.utils.CommonUtil;
|
||||||
|
import io.metersphere.service.utils.MsClassLoader;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.collections4.Predicate;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
@ -23,15 +25,18 @@ import java.io.File;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.util.LinkedList;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.UUID;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public class PluginService {
|
public class PluginService {
|
||||||
@Resource
|
@Resource
|
||||||
private PluginMapper pluginMapper;
|
private PluginMapper pluginMapper;
|
||||||
|
@Resource
|
||||||
|
private MsClassLoader classLoader;
|
||||||
|
|
||||||
public String editPlugin(MultipartFile file) {
|
public String editPlugin(MultipartFile file) {
|
||||||
String id = UUID.randomUUID().toString();
|
String id = UUID.randomUUID().toString();
|
||||||
|
@ -80,6 +85,7 @@ public class PluginService {
|
||||||
private List<PluginResourceDTO> getMethod(String path, String fileName) {
|
private List<PluginResourceDTO> getMethod(String path, String fileName) {
|
||||||
List<PluginResourceDTO> resources = new LinkedList<>();
|
List<PluginResourceDTO> resources = new LinkedList<>();
|
||||||
try {
|
try {
|
||||||
|
// classLoader.unloadJarFile(path);
|
||||||
this.loadJar(path);
|
this.loadJar(path);
|
||||||
List<Class<?>> classes = CommonUtil.getSubClass(fileName);
|
List<Class<?>> classes = CommonUtil.getSubClass(fileName);
|
||||||
for (Class<?> aClass : classes) {
|
for (Class<?> aClass : classes) {
|
||||||
|
@ -134,7 +140,10 @@ public class PluginService {
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
// 获取系统类加载器
|
// 获取系统类加载器
|
||||||
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
|
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
|
||||||
|
|
||||||
URL url = jarFile.toURI().toURL();
|
URL url = jarFile.toURI().toURL();
|
||||||
|
//URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
|
||||||
|
|
||||||
method.invoke(classLoader, url);
|
method.invoke(classLoader, url);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
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() {
|
public void loadPlugins() {
|
||||||
PluginExample example = new PluginExample();
|
PluginExample example = new PluginExample();
|
||||||
List<Plugin> plugins = pluginMapper.selectByExample(example);
|
List<Plugin> plugins = pluginMapper.selectByExample(example);
|
||||||
if (CollectionUtils.isNotEmpty(plugins)) {
|
if (CollectionUtils.isNotEmpty(plugins)) {
|
||||||
plugins.forEach(item -> {
|
plugins = plugins.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(()
|
||||||
this.loadJar(item.getSourcePath());
|
-> 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);
|
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() {
|
run() {
|
||||||
let projectId = getCurrentProjectID();
|
let projectId = getCurrentProjectID();
|
||||||
// 如果envMap不存在,是单接口调用
|
// 如果envMap不存在,是单接口调用
|
||||||
|
@ -83,6 +95,7 @@ export default {
|
||||||
}
|
}
|
||||||
threadGroup.hashTree.push(item);
|
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 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);
|
let bodyFiles = getBodyUploadFiles(reqObj, this.runData);
|
||||||
if (this.runData[0].url) {
|
if (this.runData[0].url) {
|
||||||
|
|
Loading…
Reference in New Issue