refactor(接口测试): 优化jar加载机制,动态按照项目组和类加载器
This commit is contained in:
parent
bf855e4f62
commit
129523cf70
|
@ -11,6 +11,7 @@ import io.metersphere.commons.utils.ApiFileUtil;
|
||||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||||
import io.metersphere.dto.FileInfoDTO;
|
import io.metersphere.dto.FileInfoDTO;
|
||||||
import io.metersphere.dto.ProjectJarConfig;
|
import io.metersphere.dto.ProjectJarConfig;
|
||||||
|
import io.metersphere.jmeter.ProjectClassLoader;
|
||||||
import io.metersphere.metadata.service.FileMetadataService;
|
import io.metersphere.metadata.service.FileMetadataService;
|
||||||
import io.metersphere.utils.JarConfigUtils;
|
import io.metersphere.utils.JarConfigUtils;
|
||||||
import io.metersphere.vo.BooleanPool;
|
import io.metersphere.vo.BooleanPool;
|
||||||
|
@ -57,9 +58,12 @@ public class NewDriverManager {
|
||||||
Map<String, List<ProjectJarConfig>> map = JarConfigUtils.getJarConfigs(projectIds, jarConfigMap);
|
Map<String, List<ProjectJarConfig>> map = JarConfigUtils.getJarConfigs(projectIds, jarConfigMap);
|
||||||
if (MapUtils.isNotEmpty(map)) {
|
if (MapUtils.isNotEmpty(map)) {
|
||||||
//获取文件内容
|
//获取文件内容
|
||||||
|
List<String> loaderProjectIds = new ArrayList<>();
|
||||||
|
|
||||||
FileMetadataService fileMetadataService = CommonBeanFactory.getBean(FileMetadataService.class);
|
FileMetadataService fileMetadataService = CommonBeanFactory.getBean(FileMetadataService.class);
|
||||||
map.forEach((key, value) -> {
|
map.forEach((key, value) -> {
|
||||||
if (CollectionUtils.isNotEmpty(value)) {
|
if (CollectionUtils.isNotEmpty(value)) {
|
||||||
|
loaderProjectIds.add(key);
|
||||||
//历史数据
|
//历史数据
|
||||||
value.stream().distinct().filter(s -> s.isHasFile()).forEach(s -> {
|
value.stream().distinct().filter(s -> s.isHasFile()).forEach(s -> {
|
||||||
//获取文件内容
|
//获取文件内容
|
||||||
|
@ -81,6 +85,9 @@ public class NewDriverManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始化类加载器
|
||||||
|
ProjectClassLoader.initClassLoader(loaderProjectIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jarConfigMap;
|
return jarConfigMap;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import io.metersphere.commons.constants.ScheduleGroup;
|
||||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||||
import io.metersphere.commons.utils.LogUtil;
|
import io.metersphere.commons.utils.LogUtil;
|
||||||
import io.metersphere.dto.BaseSystemConfigDTO;
|
import io.metersphere.dto.BaseSystemConfigDTO;
|
||||||
|
import io.metersphere.jmeter.ProjectClassLoader;
|
||||||
import io.metersphere.service.*;
|
import io.metersphere.service.*;
|
||||||
import io.metersphere.service.definition.ApiModuleService;
|
import io.metersphere.service.definition.ApiModuleService;
|
||||||
import io.metersphere.service.scenario.ApiScenarioModuleService;
|
import io.metersphere.service.scenario.ApiScenarioModuleService;
|
||||||
|
@ -95,6 +96,8 @@ public class ApiAppStartListener implements ApplicationRunner {
|
||||||
scheduleService.startEnableSchedules(ScheduleGroup.API_SCENARIO_TEST);
|
scheduleService.startEnableSchedules(ScheduleGroup.API_SCENARIO_TEST);
|
||||||
scheduleService.startEnableSchedules(ScheduleGroup.SWAGGER_IMPORT);
|
scheduleService.startEnableSchedules(ScheduleGroup.SWAGGER_IMPORT);
|
||||||
LogUtil.info("Startup complete");
|
LogUtil.info("Startup complete");
|
||||||
|
|
||||||
|
ProjectClassLoader.initClassLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package io.metersphere.jmeter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class loader that combines multiple class loaders into one.<br>
|
||||||
|
* The classes loaded by this class loader are associated with this class loader,
|
||||||
|
* i.e. Class.getClassLoader() points to this class loader.
|
||||||
|
*/
|
||||||
|
public class JoinClassLoader extends ClassLoader {
|
||||||
|
|
||||||
|
private ClassLoader[] delegateClassLoaders;
|
||||||
|
|
||||||
|
public JoinClassLoader(ClassLoader parent, ClassLoader... delegateClassLoaders) {
|
||||||
|
super(parent);
|
||||||
|
this.delegateClassLoaders = delegateClassLoaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
// It would be easier to call the loadClass() methods of the delegateClassLoaders
|
||||||
|
// here, but we have to load the class from the byte code ourselves, because we
|
||||||
|
// need it to be associated with our class loader.
|
||||||
|
String path = name.replace('.', '/') + ".class";
|
||||||
|
URL url = findResource(path);
|
||||||
|
if (url == null) {
|
||||||
|
throw new ClassNotFoundException(name);
|
||||||
|
}
|
||||||
|
ByteBuffer byteCode;
|
||||||
|
try {
|
||||||
|
byteCode = loadResource(url);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClassNotFoundException(name, e);
|
||||||
|
}
|
||||||
|
return defineClass(name, byteCode, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer loadResource(URL url) throws IOException {
|
||||||
|
InputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = url.openStream();
|
||||||
|
int initialBufferCapacity = Math.min(0x40000, stream.available() + 1);
|
||||||
|
if (initialBufferCapacity <= 2) {
|
||||||
|
initialBufferCapacity = 0x10000;
|
||||||
|
} else {
|
||||||
|
initialBufferCapacity = Math.max(initialBufferCapacity, 0x200);
|
||||||
|
}
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(initialBufferCapacity);
|
||||||
|
while (true) {
|
||||||
|
if (!buf.hasRemaining()) {
|
||||||
|
ByteBuffer newBuf = ByteBuffer.allocate(2 * buf.capacity());
|
||||||
|
buf.flip();
|
||||||
|
newBuf.put(buf);
|
||||||
|
buf = newBuf;
|
||||||
|
}
|
||||||
|
int len = stream.read(buf.array(), buf.position(), buf.remaining());
|
||||||
|
if (len <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf.position(buf.position() + len);
|
||||||
|
}
|
||||||
|
buf.flip();
|
||||||
|
return buf;
|
||||||
|
} finally {
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected URL findResource(String name) {
|
||||||
|
for (ClassLoader delegate : delegateClassLoaders) {
|
||||||
|
URL resource = delegate.getResource(name);
|
||||||
|
if (resource != null) {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Enumeration<URL> findResources(String name) throws IOException {
|
||||||
|
Vector<URL> vector = new Vector<URL>();
|
||||||
|
for (ClassLoader delegate : delegateClassLoaders) {
|
||||||
|
Enumeration<URL> enumeration = delegate.getResources(name);
|
||||||
|
while (enumeration.hasMoreElements()) {
|
||||||
|
vector.add(enumeration.nextElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vector.elements();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package io.metersphere.jmeter;
|
||||||
|
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
|
import io.metersphere.utils.JarConfigUtils;
|
||||||
|
import io.metersphere.utils.JsonUtils;
|
||||||
|
import io.metersphere.utils.LocalPathUtil;
|
||||||
|
import io.metersphere.utils.LoggerUtil;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class ProjectClassLoader {
|
||||||
|
// 记录以项目为单位的自定义JAR的类加载器
|
||||||
|
private final static Map<String, GroovyClassLoader> classLoaderMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static void initClassLoader(List<String> projectIds) {
|
||||||
|
// 读取所有JAR路径
|
||||||
|
for (String projectId : projectIds) {
|
||||||
|
List<String> jarPaths = JarConfigUtils.walk(LocalPathUtil.prePath + File.separator + projectId);
|
||||||
|
if (CollectionUtils.isNotEmpty(jarPaths)) {
|
||||||
|
LoggerUtil.info("加载JAR-PATH:" + JsonUtils.toJSONString(jarPaths), projectId);
|
||||||
|
// 初始化类加载器
|
||||||
|
GroovyClassLoader loader = MsClassLoader.getDynamic(jarPaths);
|
||||||
|
classLoaderMap.put(projectId, loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassLoader getClassLoader(List<String> projectIds) {
|
||||||
|
GroovyClassLoader classLoader = new GroovyClassLoader();
|
||||||
|
List<GroovyClassLoader> classLoaders = new ArrayList<>();
|
||||||
|
for (String projectId : projectIds) {
|
||||||
|
if (classLoaderMap.containsKey(projectId)) {
|
||||||
|
classLoaders.add(classLoaderMap.get(projectId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 单项目加载器
|
||||||
|
if (classLoaders.size() == 1) {
|
||||||
|
return classLoaders.get(0);
|
||||||
|
}
|
||||||
|
// 跨项目加载器
|
||||||
|
return new JoinClassLoader(classLoader, classLoaders.toArray(new ClassLoader[classLoaders.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initClassLoader() {
|
||||||
|
// 读取所有JAR路径
|
||||||
|
List<String> projectIds = JarConfigUtils.getFileNames(LocalPathUtil.prePath + File.separator);
|
||||||
|
LoggerUtil.info("初始化所有JAR:" + JsonUtils.toJSONString(projectIds));
|
||||||
|
if (CollectionUtils.isNotEmpty(projectIds)) {
|
||||||
|
initClassLoader(projectIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package io.metersphere.utils;
|
package io.metersphere.utils;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
import io.metersphere.jmeter.ProjectClassLoader;
|
||||||
import io.metersphere.jmeter.MsClassLoader;
|
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.collections4.MapUtils;
|
import org.apache.commons.collections4.MapUtils;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
@ -30,11 +29,18 @@ public class CustomizeFunctionUtil {
|
||||||
String pathStr = testPlan.getPropertyAsString(JAR_PATH);
|
String pathStr = testPlan.getPropertyAsString(JAR_PATH);
|
||||||
JMeterContext context = JMeterContextService.getContext();
|
JMeterContext context = JMeterContextService.getContext();
|
||||||
if (StringUtils.isNotEmpty(pathStr) && context != null) {
|
if (StringUtils.isNotEmpty(pathStr) && context != null) {
|
||||||
List<String> jarPaths = JsonUtils.parseObject(pathStr, List.class);
|
List<String> projectIds = JsonUtils.parseObject(pathStr, List.class);
|
||||||
LoggerUtil.info(testPlan.getName() + "加载JAR:", jarPaths);
|
LoggerUtil.info("加载JAR-PROJECT-ID:" + projectIds, testPlan.getName());
|
||||||
if (CollectionUtils.isNotEmpty(jarPaths)) {
|
LoggerUtil.info("PRE-PATH:" + LocalPathUtil.prePath, testPlan.getName());
|
||||||
|
if (CollectionUtils.isNotEmpty(projectIds)) {
|
||||||
|
// 读取所有JAR路径
|
||||||
|
List<String> jarPaths = JarConfigUtils.findPathByProjectIds(projectIds);
|
||||||
|
LoggerUtil.info("加载JAR-PATH:" + JsonUtils.toJSONString(jarPaths), testPlan.getName());
|
||||||
|
if (CollectionUtils.isEmpty(jarPaths)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 初始化类加载器
|
// 初始化类加载器
|
||||||
GroovyClassLoader loader = MsClassLoader.getDynamic(jarPaths);
|
ClassLoader loader = ProjectClassLoader.getClassLoader(projectIds);
|
||||||
if (loader == null) {
|
if (loader == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,16 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.aspectj.util.FileUtil;
|
import org.aspectj.util.FileUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class JarConfigUtils {
|
public class JarConfigUtils {
|
||||||
|
|
||||||
|
@ -99,4 +104,24 @@ public class JarConfigUtils {
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<String> findPathByProjectIds(List<String> projectIds) {
|
||||||
|
List<String> jarPaths = new ArrayList<>();
|
||||||
|
if (CollectionUtils.isNotEmpty(projectIds)) {
|
||||||
|
projectIds.forEach(item -> {
|
||||||
|
jarPaths.addAll(walk(LocalPathUtil.prePath + File.separator + item));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return jarPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<String> walk(String dirName) {
|
||||||
|
try (Stream<Path> paths = Files.walk(Paths.get(dirName), 2)) {
|
||||||
|
return paths.map(path -> path.toString()).filter(f -> f.endsWith(".jar")).collect(Collectors.toList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LoggerUtil.error("读取文件路径异常:", e);
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,10 +168,11 @@ public abstract class JSR223TestElement extends ScriptingTestElement
|
||||||
// 设置自定义类加载器
|
// 设置自定义类加载器
|
||||||
Object dynamicClassLoader = JMeterContextService.getContext().getVariables().getObject(CustomizeFunctionUtil.MS_CLASS_LOADER);
|
Object dynamicClassLoader = JMeterContextService.getContext().getVariables().getObject(CustomizeFunctionUtil.MS_CLASS_LOADER);
|
||||||
if (dynamicClassLoader != null) {
|
if (dynamicClassLoader != null) {
|
||||||
GroovyClassLoader classLoader = (GroovyClassLoader) dynamicClassLoader;
|
ClassLoader classLoader = (ClassLoader) dynamicClassLoader;
|
||||||
if (scriptEngine instanceof GroovyScriptEngineImpl) {
|
if (scriptEngine instanceof GroovyScriptEngineImpl) {
|
||||||
|
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
|
||||||
GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine;
|
GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine;
|
||||||
groovyScriptEngine.setClassLoader(classLoader);
|
groovyScriptEngine.setClassLoader(groovyClassLoader);
|
||||||
} else {
|
} else {
|
||||||
Thread.currentThread().setContextClassLoader(classLoader);
|
Thread.currentThread().setContextClassLoader(classLoader);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue