refactor(接口测试): 优化jar加载机制,动态按照项目组和类加载器
This commit is contained in:
parent
80fae150a3
commit
183e86a55a
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue