feat(插件管理): 优化插件加载机制

This commit is contained in:
fit2-zhao 2021-09-09 19:13:59 +08:00 committed by fit2-zhao
parent 54a2147f8d
commit b4f5f710db
4 changed files with 158 additions and 6 deletions

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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) {