在用 JMeter 做性能测试时,不可避免的需要操作线程中的参数,不同的接口需要接收的输入,以及他们响应结果都需要在运行过程中做动态处理。 JMeter 提供了 JSR223 取样器/前置处理器/后置处理器, 开发者可以在其中写脚本,那脚本时如何生效的?似乎在普通的 Web 开发中很少涉及到相关的内容。
JSR233 是一个标准/规范,它定义了一种接口,使得 Java 应用程序能够与脚本语言(如 JavaScript、Python、Ruby 等)进行互操作,在 Java 6 中引入,并通过 javax.script
包提供。
所有希望自己的脚本语言能在 Java 环境中运行的开发者需要提供相应的脚本引擎实现。
例如:
JavaScript 脚本引擎,在 Java6 到 Java14 中包含在 JDK 中(不同的版本中可以一定是同一个引擎),Java15 以及以后就移除了了它,开发之需要在项目中单独引入。
groovy 脚本引擎,开发者如果需要使用 groovy 作为动态语言就需要单独引入相关的依赖,Maven 为例:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-jsr223</artifactId>
<version>3.0.21</version>
</dependency>
1、 项目中引入依赖
2、 将 Groovy 脚本准备好,一个简单的算法,写入到任意一个文件中
import java.util.stream.Collectors
List<String> getMaxLengthStrings(String s) {
String[] target = s.split("");
List<String> storage = new ArrayList<>();
StringBuilder transformable = new StringBuilder();
for (int i = 0; i < target.length; i++) {
transformable.append(target[i]);
for (int j = i + 1; j < target.length; j++) {
if (transformable.toString().contains(target[j])) {
break;
}
transformable.append(target[j]);
}
storage.add(transformable.toString());
transformable = new StringBuilder();
}
// 获取最长不重复连续字符串,可能有多个
List<String> result = storage.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.groupingBy(String::length))
.get(storage.stream().max(Comparator.comparingInt(String::length)).get().length());
return result;
}
3、 启动 Java 程序,在程序运行过程中使用脚本
public static void main(String[] args) throws ScriptException, NoSuchMethodException, IOException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
String groovyScript = FileUtils.readFileToString(new File("/Users/xxx/algorithm.log"), "UTF-8");
engine.eval(groovyScript);
Invocable invocable = (Invocable) engine;
Object obj = invocable.invokeFunction("getMaxLengthStrings", "abcccccsdfioqccabc");
if (obj instanceof List<?>) {
System.out.println(obj);
}
}
2、 使用 FileUtils(来自 commons-io 的实现 )方法读取本地文件中的代码脚本
3、 调用 eval 方法执行脚本,此时会将脚本代码转换成中可运行的 Java 类,通过执行后返回当前类的运行结果
4、通过 invokeFunction 调用脚本中全局函数, 指定函数名 getMaxLengthStrings, 指定入参: abcccccsdfioqccabc, 即可返回函数的运行结果
private static void test() throws ScriptException, NoSuchMethodException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
List<String> list = new ArrayList<>();
list.add("你好");
Bindings bindings = engine.createBindings();
bindings.put("list", list);
engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
// list会使用Binding中设置的参数
String groovyScript = " List<String> getResult() {\n" +
" list.add(\"Hello World\");\n" +
" return list;\n" +
" }";
engine.eval(groovyScript, bindings);
Invocable invocable = (Invocable) engine;
// 调用全局函数
Object obj = invocable.invokeFunction("getResult");
if (obj instanceof List<?>) {
System.out.println(obj);
}
}
解释:与上一个不一样的是, 指定了一个参数列表 Bindings , 通过 Bindings 中设置数据,用于 Java 代码和脚本之间的数据传递,Bindings 传递的对象是全局可用的, 可以全局函数中直接使用。
调用脚本中类的特定方法,返回处理后的结果
private static void test2() throws ScriptException, NoSuchMethodException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
List<String> list = new ArrayList<>();
list.add("小明");
Bindings bindings = engine.createBindings();
bindings.put("list", list);
engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
String groovyScript = "class ScriptTest {\n" +
" List list\n" +
" ScriptTest(List list){\n" +
" this.list = list\n" +
" }\n" +
" List<String> ScriptTestResult(){\n" +
" list.add(\"小王\")\n" +
" return list\n" +
" }\n" +
"}\n" +
"scriptTest = new ScriptTest(list)";
engine.eval(groovyScript, bindings);
Invocable invocable = (Invocable) engine;
Object ScriptTest = engine.get("scriptTest");
Object obj = invocable.invokeMethod(ScriptTest,"ScriptTestResult");
if (obj instanceof List<?>) {
System.out.println(obj);
}
}
ScriptEngineManager:管理和发现可用的脚本引擎。
ScriptEngine:表示一个具体的脚本引擎实例,用于执行脚本代码。
Bindings:用于在脚本引擎和 Java 之间传递变量和对象。
ScriptContext:表示脚本作用范围(例如,ENGINE_SCOPE
和 GLOBAL_SCOPE
)。