第八章. JSR223 与 Groovy:JMeter 的核武器

第八章. JSR223 与 Groovy:JMeter 的核武器

摘要:在前面的章节中,我们通过鼠标点选 GUI 组件完成了大部分任务。但真实业务往往比这复杂得多:比如接口需要 “MD5 加密签名”、需要 “AES 解密响应”、或者需要把数据 “写入本地 Excel”。面对这些需求,GUI 界面束手无策。本章我们将解锁 JMeter 的 JSR223 组件配合 Groovy 语言,让你拥有直接编写代码操控压测逻辑的能力。

本章学习路径

我们将从面板认知开始,一步步掌握脚本编程:

  • 8.1 认识 JSR223 组件
    • 8.1.1 JSR223 是什么?为什么不是 BeanShell?
    • 8.1.2 面板功能区详解(入口与配置)
  • 8.2 Groovy 语言速成(面向 Java 开发者)
    • 8.2.1 为什么它是 JMeter 的御用语言
    • 8.2.2 核心语法差异:丢掉分号与类型
  • 8.3 JMeter 的四大内置对象
    • 8.3.1 log:你的调试眼睛
    • 8.3.2 vars:变量的搬运工
    • 8.3.3 propsctx:跨线程与上下文(了解)
  • 8.4 实战:手写 MD5 加密前置处理器
    • 8.4.1 引入 Java 工具类
    • 8.4.2 编写并调试加密脚本
  • 8.5 实战:自定义数据写入后置处理器
    • 8.5.1 文件流操作
    • 8.5.2 将订单号落盘保存

8.1. 认识 JSR223 组件

在 “添加” 菜单中,你会发现很多带有 “脚本” 字样的组件,最著名的就是 BeanShellJSR223。请记住一句话:在 2025 年,请彻底遗忘 BeanShell,只用 JSR223 + Groovy。

8.1.1. 核心概念:Interface vs Language

  • JSR223:这是一个 Java 规范(Java Specification Request 223),它定义了一个标准接口,允许 Java 程序调用各种脚本语言(如 Python, Ruby, Groovy)。在 JMeter 中,它是一个 容器
  • Groovy:这才是我们要写的 语言。它完全兼容 Java 语法,但更简洁。
  • 为什么要用它?
    • BeanShell 是解释执行的,性能极差(并发一高就会卡死)。
    • Groovy 支持 “编译缓存”,JMeter 会把它编译成原生的 .class 字节码运行,性能几乎等同于原生 Java。

8.1.2. 面板功能区详解

让我们先找到并打开这个组件。

操作步骤

  1. 右键点击 线程组 -> 添加 -> 取样器 (Sampler) -> JSR223 取样器
  2. 你会看到如下界面,我们需要关注三个核心区域:

关键配置项说明

  1. Language (语言)
    • 必须选择 groovy。千万不要选 javabeanshell,否则无法享受性能优化。
  2. Cache compiled script if available (缓存编译脚本)
    • 必须勾选。这是性能起飞的关键。勾选后,这段代码只会被编译一次,之后 100 万次循环都直接运行机器码。
  3. Script (脚本编辑区)
    • 这里就是我们写代码的地方。虽然它像个记事本,没有代码提示,但它支持标准的 Java/Groovy 语法。

8.2. Groovy 语言速成

对于已经掌握 Spring Boot 的你来说,学习 Groovy 是 “零成本” 的。因为 任何合法的 Java 代码都是合法的 Groovy 代码

你可以直接在脚本区写 System.out.println("Hello");,它是能跑的。但 Groovy 提供了一些 “语法糖”,让代码更简洁。

8.2.1. 核心语法差异表

特性Java 写法Groovy 写法 (推荐)优势
分号String name = "Jack";String name = "Jack"可以省略分号,代码更干净。
类型定义String id = "123";def id = "123"使用 def 自动推断类型,类似 JS 的 let。
字符串插值"ID is " + id"ID is ${id}"使用双引号 + ${} 直接拼接变量。
Get/Setuser.getName()user.name自动调用 getter/setter 方法。

建议:作为初学者,为了避免出错,你完全可以 直接写标准的 Java 代码。等你熟练了,再尝试 Groovy 的简化写法。


8.3. JMeter 的四大内置对象

在 Script 编辑区写代码时,JMeter 已经默默地往这一小块空间里注入了几个 “上帝对象”。你不需要 new,直接就能用。

8.3.1. log:你的调试眼睛

在 GUI 界面写代码没有断点调试,我们只能靠打印日志来观察变量。

  • 代码
    1
    2
    log.info("这是普通信息");
    log.error("这是报错信息");
  • 查看位置:点击 JMeter 界面右上角的 黄色感叹号图标,底部会弹出一个控制台窗口,你的日志就显示在那里。

8.3.2. vars:变量的搬运工(最重要)

varsJMeterVariables 类的实例。它连接了 GUI 组件代码世界

  • 读取变量(从 GUI -> 代码):假设你在 “用户定义的变量” 中定义了 target_host

    1
    String ip = vars.get("target_host"); // 获取变量值
  • 写入/修改变量(从 代码 -> GUI):假设你想把计算好的结果传给下一个 HTTP 请求。

    1
    vars.put("new_token", "xwq89-sdsd-223"); // 创建名为 new_token 的变量

8.4. 实战 A:前置处理器 - 攻克 MD5 签名校验

在真实的企业级开发中,为了防止请求被篡改,后端往往要求前端对核心参数进行加密签名。例如:注册接口要求密码必须传输 32 位 MD5 密文,如果传输明文直接报错。

JMeter 的 GUI 组件没有自带 MD5 加密功能,这时候就轮到 JSR223 前置处理器大显身手了。

8.4.1. 第一步:改造靶场(制造困难)

为了模拟这个场景,我们需要先在 Spring Boot 项目中增加一个“强制校验 MD5”的注册接口。

文件路径src/main/java/com/demo/jmeterdemo/controller/AuthController.java

请在 AuthController 类中追加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 4. 模拟高安全级别的注册接口
* 规则:password 字段严禁传输明文,必须是 32 位的 MD5 字符串
*/
@PostMapping("/register")
public Map<String, Object> register(@RequestBody Map<String, String> user) {
Map<String, Object> result = new HashMap<>();
String password = user.get("password");

// 模拟后端校验:如果长度不是 32 位,说明不是标准的 MD5,直接拒绝
if (password == null || password.length() != 32) {
result.put("code", 400);
result.put("msg", "SecurAity Error: Password must be MD5 encrypted!");
return result;
}

// 校验通过
result.put("code", 200);
result.put("msg", "Register Success");
result.put("username", user.get("username"));
return result;
}

操作提醒

  1. 粘贴代码后,请重启 Spring Boot 项目。
  2. 确保控制台无报错,端口 8080 正常监听。

8.4.2. 第二步:遭遇失败(复现问题)

我们先尝试用常规方式去请求,看看会发生什么。

  1. 在 JMeter 线程组下新建一个 HTTP 请求,命名为 " API_注册 "
  2. Method: POST
  3. Path: /api/auth/register
  4. Body Data:
    1
    2
    3
    4
    {
    "username": "admin",
    "password": "123456"
    }
  5. 运行测试,查看 察看结果树
    • 响应结果{"code":400, "msg":"Security Error: Password must be MD5 encrypted!"}
    • 分析:后端校验生效,传输明文 “123456” 被拒绝。我们需要在发送请求 之前,把 “123456” 变成 MD5 密文。

8.4.3. 第三步:脚本编程(JSR223 救场)

我们需要使用 前置处理器 (PreProcessor),它的执行时机是在 HTTP 请求发送 之前

操作流程

  1. 修改 Body:将明文密码替换为变量占位符。

    1
    2
    3
    4
    {
    "username": "admin",
    "password": "${md5_pwd}"
    }

    (注:变量 ${md5_pwd} 目前还不存在,我们马上用代码生成它)

  2. 添加组件:右键点击 " API_注册_成功 " -> 添加 -> 前置处理器 -> JSR223 预处理程序

  3. 配置面板

    • 语言:选择 groovy(必须!)。
    • 缓存:勾选 Cache compiled script if available

编写 Groovy 脚本

请在 Script 编辑区输入以下代码。这段代码利用了 JMeter 自带的 commons-codec 库,这是 Java 处理加密的标准姿势。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 导入加密工具类 (JMeter 自带,无需下载 jar 包)
import org.apache.commons.codec.digest.DigestUtils

// 2. 定义原始明文密码
String rawPass = "123456";

// 3. 执行 MD5 加密A
// md5Hex 是静态方法,会将字符串转换为 32 位小写 Hex 字符串
String encryptedPass = DigestUtils.md5Hex(rawPass);

// 4. 【调试关键】打印日志
// 这一步非常重要,如果不打日志,出错了你都不知道是算错了还是没传过去
log.info("========== MD5 加密开始 ==========");
log.info("原始密码: " + rawPass);
log.info("加密结果: " + encryptedPass);
log.info("=================================");

// 5. 【核心动作】将结果存入 JMeter 变量池
// "md5_pwd" 对应我们在 Body 中写的 ${md5_pwd}
vars.put("md5_pwd", encryptedPass);

8.4.4. 第四步:全链路验证(闭环检查)

脚本写好了,能不能跑通?我们需要检查三个地方。

操作步骤

  1. 打开日志监视器:点击 JMeter 右上角的黄色感叹号图标(或菜单栏 选项 -> 日志查看器),清空旧日志。
  2. 运行脚本:点击启动按钮。
  3. 检查点 1:看日志
    • 观察下方控制台,是否输出了 加密结果: e10adc3949ba59abbe56e057f20f883e
    • 如果有,说明 Groovy 代码运行正常,加密逻辑成功。
  4. 检查点 2:看请求体 (Request Body)
    • 察看结果树 中选中 " API_注册_成功 "。
    • 点击 请求 (Request) 选项卡 -> Request Body。
    • 观察 password 字段:"password": "e10adc3949ba59abbe56e057f20f883e"
    • 说明 vars.put 生效了,变量成功替换了占位符。
  5. 检查点 3:看响应 (Response)
    • 点击 响应数据 (Response Data)
    • 看到 {"code":200, "msg":"Register Success"}
    • 说明后端校验通过。

通过这四步,我们完整实现了一个 “Java 加密 -> JMeter 变量 -> HTTP 请求” 的数据流转。


8.5. 实战 B:后置处理器 - 核心数据落盘保存

在压测过程中,我们经常需要把生产出来的数据(比如:注册成功的用户名、下单成功的订单号)保存下来,作为下一轮压测的输入数据,或者发给其他部门进行对账。

JMeter 的 “保存响应到文件” 组件功能很弱(只能存整个响应),要想灵活地只存一个 ID,必须使用 JSR223 后置处理器

8.5.1. 第一步:确认数据源

我们要保存的是 下单接口 返回的 orderId

  1. 确保你已经有了 " API_下单 " 接口(参考第 5 章)。
  2. 确保该接口下挂载了 JSON 提取器
    • 变量名称: orderId
    • JSON 路径: $.orderId

8.5.2. 第二步:编写落盘脚本

我们需要在提取出 orderId 之后,把它写入电脑的硬盘里。

操作步骤

  1. 右键点击 " API_下单 " -> 添加 -> 后置处理器 -> JSR223 后置处理程序
    • 注意顺序:它必须放在 “JSON 提取器” 的 下方(因为要先提取,再写入)。
  2. 配置面板:语言选 groovy,勾选缓存。

编写 Groovy 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 1. 从 JMeter 变量池获取 OrderID
// 这个变量是由上面的 JSON 提取器生成的
String id = vars.get("orderId");

// 2. 安全校验:如果提取失败,绝对不能写入
// 否则文件里会有一堆 "null" 或默认值,导致数据污染
if (id == null || id.equals("null")) {
log.error(">>> 严重警告:未能获取到订单ID,跳过写入操作!");
return; // 直接终止当前脚本,不执行后面的写入
}

// 3. 定义文件路径
// 建议使用正斜杠 /,它在 Windows 和 Mac/Linux 上都能通用
// 请确保 D 盘存在,或者修改为你电脑上存在的路径(如 /tmp/orders.csv)
String filePath = "D:/jmeter_orders.csv";

try {
// 4. 创建文件写入流
// 参数 true 表示 "Append Mode" (追加模式)
// 如果设为 false,每次运行都会清空旧文件,只存最后一条,这通常不是我们想要的
FileWriter fwriter = new FileWriter(filePath, true);
BufferedWriter out = new BufferedWriter(fwriter);

// 5. 写入数据并换行
out.write(id);
out.write("\n"); // 必须换行,否则所有 ID 会连成一长串

// 6. 关闭流 (极其重要!)
// 如果不关闭,数据可能卡在缓冲区里,文件里就是空的
out.close();
fwriter.close();

// 打印成功日志,方便确认
log.info("成功保存订单ID: " + id);

} catch (Exception e) {
// 如果文件被占用或没有权限,打印错误堆栈
log.error("写入文件失败: " + e.getMessage());
}

8.5.3. 第三步:验证数据落盘

代码写得再漂亮,文件里有数据才是硬道理。

验证流程

  1. 清理环境:如果 D:/jmeter_orders.csv 已经存在,建议先手动删除它,确保我们看到的是新的。
  2. 运行脚本:设置线程组循环 5 次,点击启动。
  3. 观察 JMeter
    • 查看日志窗口,应该有 5 条 成功保存订单ID: xxxx 的记录。
    • 确保没有红色的 写入文件失败 报错。
  4. 检查硬盘文件
    • 打开 D:/ 盘(或你设置的路径)。
    • 找到 jmeter_orders.csv,用记事本打开。
    • 预期结果:应该看到 5 行不同的数字 ID。
1
2
3
4
1731988888123
1731988889456
1731988890789
...

如果能看到这个文件,恭喜你,你已经掌握了用 JMeter 处理复杂数据流的核心技能。


8.6. 本章小结

本章我们跨越了 GUI 的边界,进入了代码的领域。这是从中级测试工程师迈向高级的关键一步。

核心要点

  1. 工具链:坚决使用 JSR223 + Groovy,配合 Cache 选项,性能是 BeanShell 的百倍。
  2. 调试法:脚本是看不见摸不着的,必须依赖 log.info() 打印关键变量,通过日志控制台来 “透视” 运行过程。
  3. 数据流
    • GUI -> 代码vars.get("key")
    • 代码 -> GUIvars.put("key", "value")
  4. 安全性:在进行文件读写等高危操作时,务必进行 判空校验 (null check)异常捕获 (try-catch),防止因为一条脏数据导致整个测试中断。