From b4f5f710dbd9452628f058e1ee931e1b54dd9152 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 9 Sep 2021 19:13:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8F=92=E4=BB=B6=E5=8A=A0=E8=BD=BD=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/service/PluginService.java | 30 ++++++-- .../service/utils/MsClassLoader.java | 53 +++++++++++++++ .../service/utils/MsURLClassLoader.java | 68 +++++++++++++++++++ .../api/definition/components/Run.vue | 13 ++++ 4 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/service/utils/MsClassLoader.java create mode 100644 backend/src/main/java/io/metersphere/service/utils/MsURLClassLoader.java diff --git a/backend/src/main/java/io/metersphere/service/PluginService.java b/backend/src/main/java/io/metersphere/service/PluginService.java index 3c37fc15b8..27c21ae851 100644 --- a/backend/src/main/java/io/metersphere/service/PluginService.java +++ b/backend/src/main/java/io/metersphere/service/PluginService.java @@ -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 getMethod(String path, String fileName) { List resources = new LinkedList<>(); try { + // classLoader.unloadJarFile(path); this.loadJar(path); List> 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 Predicate distinctByName(Function keys) { + Map sen = new ConcurrentHashMap<>(); + return t -> sen.putIfAbsent(keys.apply(t), Boolean.TRUE) == null; + } + public void loadPlugins() { PluginExample example = new PluginExample(); List 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()); + }); + } } } diff --git a/backend/src/main/java/io/metersphere/service/utils/MsClassLoader.java b/backend/src/main/java/io/metersphere/service/utils/MsClassLoader.java new file mode 100644 index 0000000000..b4f438680d --- /dev/null +++ b/backend/src/main/java/io/metersphere/service/utils/MsClassLoader.java @@ -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 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); + } +} diff --git a/backend/src/main/java/io/metersphere/service/utils/MsURLClassLoader.java b/backend/src/main/java/io/metersphere/service/utils/MsURLClassLoader.java new file mode 100644 index 0000000000..2402213731 --- /dev/null +++ b/backend/src/main/java/io/metersphere/service/utils/MsURLClassLoader.java @@ -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; + } + +} diff --git a/frontend/src/business/components/api/definition/components/Run.vue b/frontend/src/business/components/api/definition/components/Run.vue index f81fc2d609..3866d4c9bf 100644 --- a/frontend/src/business/components/api/definition/components/Run.vue +++ b/frontend/src/business/components/api/definition/components/Run.vue @@ -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) {