refactor(接口测试): 优化jar加载机制,动态按照项目组和类加载器

This commit is contained in:
fit2-zhao 2023-02-10 17:25:16 +08:00 committed by fit2-zhao
parent bf855e4f62
commit 129523cf70
7 changed files with 202 additions and 8 deletions

View File

@ -11,6 +11,7 @@ import io.metersphere.commons.utils.ApiFileUtil;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.dto.FileInfoDTO;
import io.metersphere.dto.ProjectJarConfig;
import io.metersphere.jmeter.ProjectClassLoader;
import io.metersphere.metadata.service.FileMetadataService;
import io.metersphere.utils.JarConfigUtils;
import io.metersphere.vo.BooleanPool;
@ -57,9 +58,12 @@ public class NewDriverManager {
Map<String, List<ProjectJarConfig>> map = JarConfigUtils.getJarConfigs(projectIds, jarConfigMap);
if (MapUtils.isNotEmpty(map)) {
//获取文件内容
List<String> loaderProjectIds = new ArrayList<>();
FileMetadataService fileMetadataService = CommonBeanFactory.getBean(FileMetadataService.class);
map.forEach((key, value) -> {
if (CollectionUtils.isNotEmpty(value)) {
loaderProjectIds.add(key);
//历史数据
value.stream().distinct().filter(s -> s.isHasFile()).forEach(s -> {
//获取文件内容
@ -81,6 +85,9 @@ public class NewDriverManager {
}
}
});
// 初始化类加载器
ProjectClassLoader.initClassLoader(loaderProjectIds);
}
}
return jarConfigMap;

View File

@ -7,6 +7,7 @@ import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.jmeter.ProjectClassLoader;
import io.metersphere.service.*;
import io.metersphere.service.definition.ApiModuleService;
import io.metersphere.service.scenario.ApiScenarioModuleService;
@ -95,6 +96,8 @@ public class ApiAppStartListener implements ApplicationRunner {
scheduleService.startEnableSchedules(ScheduleGroup.API_SCENARIO_TEST);
scheduleService.startEnableSchedules(ScheduleGroup.SWAGGER_IMPORT);
LogUtil.info("Startup complete");
ProjectClassLoader.initClassLoader();
}

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package io.metersphere.utils;
import groovy.lang.GroovyClassLoader;
import io.metersphere.jmeter.MsClassLoader;
import io.metersphere.jmeter.ProjectClassLoader;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
@ -30,11 +29,18 @@ public class CustomizeFunctionUtil {
String pathStr = testPlan.getPropertyAsString(JAR_PATH);
JMeterContext context = JMeterContextService.getContext();
if (StringUtils.isNotEmpty(pathStr) && context != null) {
List<String> jarPaths = JsonUtils.parseObject(pathStr, List.class);
LoggerUtil.info(testPlan.getName() + "加载JAR:", jarPaths);
if (CollectionUtils.isNotEmpty(jarPaths)) {
List<String> projectIds = JsonUtils.parseObject(pathStr, List.class);
LoggerUtil.info("加载JAR-PROJECT-ID:" + projectIds, testPlan.getName());
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) {
return;
}

View File

@ -6,11 +6,16 @@ import org.apache.commons.lang3.StringUtils;
import org.aspectj.util.FileUtil;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JarConfigUtils {
@ -99,4 +104,24 @@ public class JarConfigUtils {
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<>();
}
}

View File

@ -168,10 +168,11 @@ public abstract class JSR223TestElement extends ScriptingTestElement
// 设置自定义类加载器
Object dynamicClassLoader = JMeterContextService.getContext().getVariables().getObject(CustomizeFunctionUtil.MS_CLASS_LOADER);
if (dynamicClassLoader != null) {
GroovyClassLoader classLoader = (GroovyClassLoader) dynamicClassLoader;
ClassLoader classLoader = (ClassLoader) dynamicClassLoader;
if (scriptEngine instanceof GroovyScriptEngineImpl) {
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine;
groovyScriptEngine.setClassLoader(classLoader);
groovyScriptEngine.setClassLoader(groovyClassLoader);
} else {
Thread.currentThread().setContextClassLoader(classLoader);
}