vars.get() 与 ${}引用符在获取变量值时的注意事项
前言
vars.get("") 或 ${} 引用符都可以用来获取Jmeter中变量值,但两者是有差别的。除了使用场景的限制,当脚本结构或内容有点复杂(此时脑子容易不清醒的情况下),在使用vars.get("") 或 ${} 引用符获取变量值时,就需要留心了。
踩坑之 当vars.get("变量名") 被写成了vars.get("${变量名}"),就是用力过度,就获取不到预期的变量值了呀
1、上配置
(1)线程组,单用户多次循环
(2)使用计数器变量 - caseCount

(3)在JSR223取样器中,处理变量值

(4)在JAVA请求中打印变量值

2、看运行情况
程序没有报错,但是caseCount的值没获取到,直接是null了。。。


3、分析请教
(1) 问题分析与排查
啥也别说了,先配上 调试取样器,看看是啥情况




就是null啊,还有啥好犹豫的。。。。。。对着屏幕和代码,静心地琢磨一会儿……竟然发现了细节中的魔鬼……我这个变量caseCount是怎么获取的啊。。。。
4、调试与解决
(1)说干就干,调整一下



完美解决了JAVA请求的输出值,但是意外发现cc的值为空哦。。。好吧,再看上面JSR223的代码,不是空哪里跑!调整一下,重新执行吧,善始善终……



(2)深度请教
原来,偶尔看过一点说vars.get('') 和 ${} 在获取变量时,是有区别的,干脆请DS帮忙深挖一下
1 Shannon:
2 Jmeter中 vars.get()和$()在使用上有什么区别?
3
4
5 Deep Seek:
6 了解JMeter中`vars.get()`和`${}`的区别,对你编写脚本和调试都很有帮助。
7 简单来说,它们虽然都用于获取变量值,但在使用范围、解析时机和返回值类型上有所不同。
8 我来为你详细解释一下。
9
10 # 🧾 变量引用方式对比
11
12 JMeter中的这两种变量引用方式主要区别如下:
13
14 | **特性** | **${} 方式** | **vars.get() 方式** |
15 | :--------------- | :----------------------------------------------- | :--------------------------------------------------- |
16 | **使用范围** | 几乎适用于JMeter任何地方(如HTTP请求参数、断言等) | 主要用于编写脚本的元件(如BeanShell, JSR223 Sampler等) |
17 | **返回值类型** | 字符串(String) | 对象(Object) |
18 | **解析时机** | 在脚本**执行前**解析 | 在脚本**执行时**解析 |
19 | **性能影响** | 每次引用都会触发一次解析 | 直接访问变量值,性能较好 |
20 | **线程安全性** | 线程局部变量 | 线程局部变量 |
21 | **适用场景** | 静态配置、简单变量引用 | 动态处理、逻辑判断、对象操作 |
22
23 # 🧩 关键差异详解
24
25 ## 1. 使用范围不同
26 - **${varName}** 的使用范围更广,你可以在JMeter测试计划的很多地方使用它,比如HTTP请求的参数、头部、断言条件,定时器的延迟时间等。
27 - **vars.get("varName")** 通常只在能够编写脚本的元件中使用,例如:
28 * BeanShell Sampler/PostProcessor/PreProcessor
29 * JSR223 Sampler/PostProcessor/PreProcessor (使用Groovy, JavaScript等语言)
30
31 在脚本外部直接使用 `vars.get("varName")` 是无效的。
32
33 ## 2. 返回值类型不同
34 - **${varName}** 获取的值总是**字符串类型**。即便变量存储的是数字或其他对象,`${}`也会将其转换为字符串返回。
35 - **vars.get("varName")** 返回的是**Object类型**,也就是变量实际存储的类型。这为你提供了更大的灵活性,
尤其是在BeanShell或JSR223脚本中需要进行逻辑判断或数学运算时。
36
37 例如,如果变量存储的是数字,`vars.get()`返回的可以是Integer、Long等,方便直接进行算术运算,而无需手动转换类型。
38
39 ## 3. 解析时机不同
40 这是一个非常重要的区别,也常常是导致困惑的地方。
41
42 - **${varName}** 的解析发生在**脚本执行之前**。
43 JMeter会在运行你的脚本或采样器之前,先扫描所有字段,并将其中的`${varName}`替换成该变量**当前的值**。
44 一旦替换完成,这个值在本次执行中就成了一个**固定的字符串**,不会再改变。
45 - **vars.get("varName")** 的解析发生在**脚本执行之时**。它会在运行时**实时**地去变量存储中获取**最新的变量值**。
46
47 这个区别导致了以下现象:
48 ```java
49 // 假设变量 "initialValue" 的初始值为 "100"
50 vars.put("dynamicVar", "200"); // 在脚本中修改变量值
51
52 log.info("通过 ${} 获取: " + "${initialValue}"); // 输出: 100 (仍然是修改前的值)
53 log.info("通过 vars.get() 获取: " + vars.get("dynamicVar")); // 输出: 200 (获取到修改后的值)
54 ```
55 在上面的例子中,即使在脚本中通过`vars.put`修改了`initialValue`的值,但通过`${initialValue}`获取到的还是它**在脚本开始执行前那一刻的值**。
56 而`vars.get("dynamicVar")`则能立即反映出变量的变化。
57 ## 4. 性能与脚本缓存
58 当你在**JSR223元件**(推荐使用,性能优于BeanShell)中并启用了**Cache compiled script**选项时,使用`${varName}`可能会导致脚本缓存不符合预期。
59
60 因为缓存的是第一次编译的脚本,而第一次编译时`${varName}`已经被替换成了固定的字符串值。
61 即使后续迭代中变量值发生变化,脚本也不会重新编译,它使用的还是第一次的“快照”值。
62
63 而使用`vars.get("varName")`,每次执行都会动态获取最新的值,与脚本缓存机制配合得更好。
64
65 # 🚀 适用场景与建议
66
67 了解区别后,如何在实践中选择呢?
68
69 - **在非脚本类元件中配置静态参数**:在HTTP请求、JDBC请求、定时器等元件的配置字段中,需要引用变量时,**使用 `${}`**。这是唯一的选择。
70 - **在脚本中进行逻辑处理或动态赋值**:在BeanShell/JSR223脚本中,如果你需要:
71 * **获取变量最新的值**(尤其是刚刚被前一个采样器或脚本修改过的变量)
72 * **进行类型转换或运算**
73 * **判断变量是否存在**(`vars.get()`返回`null`如果变量不存在,而`${}`会原样输出字符串`${varName}`)
74 **请使用 `vars.get()`**。
75 - **在脚本中设置变量**:在脚本中设置新变量或修改现有变量,**必须使用 `vars.put("varName", value)`**。你不能通过`${varName} = newValue`这种方式来赋值。
76
77 # ⚠️ 常见误区与注意事项
78
79 1. **错误拼接**:在脚本中,不要尝试这样写:`vars.get("${prefix}_suffix")`。正确的做法是先获取前缀变量的值,再进行拼接。
80 ```java
81 // 错误写法
82 // String wrongValue = vars.get("${prefix}_suffix");
83
84 // 正确写法
85 String prefixValue = vars.get("prefix"); // 或者直接是字符串,如 "error"
86 String correctValue = vars.get(prefixValue + "_suffix");
87 ```
88
89 2. **变量未定义的情况**:对于未定义的变量,`${undefinedVar}`会原样输出字符串`${undefinedVar}`,而`vars.get("undefinedVar")`会返回`null`。
90 在脚本中判断变量是否存在时,利用`vars.get()`返回`null`的特性更为方便。
91
92 3. **性能考量**:在循环次数非常多的高并发测试场景中,在JSR223脚本中使用`vars.get()`通常比依赖`${}`的预解析性能稍好,因为它避免了字符串的解析过程。
93
94 # 💎 总结
95
96 简单来说,`${}`**是配置时用的**,值在开始执行时就固定了;`vars.get()`**是脚本里实时查询用的**,总能拿到最新值。
97
98 希望这些解释能帮你避免一些常见的坑。灵活运用它们,能让你的JMeter脚本更加强大和灵活。
根据上面的内容,我觉得我有必要根据其中的关键差异,搞两个脚本验证一下。
① 两者返回值类型不同
${varName} 获取的总是字符串类型,无论原变量存储的是什么类型;而vars.get("varName")返回的是Object类型,也就是变量实际存储的类型。来验证一下吧。


大惊失色!测试的结果是,vars.get()获取的变量,类型总是String,而不是如上面说的,能够获取到变量本来的数据类型。。。。这个结论告诉我们。。。。Deep Seek的答案,也会出错!!! 实践是检验真理的唯一标准。。。!!!
再继续测试,看一下${'varName'}的数据类型是什么,结论也出来了—— org.codehaus.groovy.runtime.GStringImpl ——实在是一张有点新的面孔下,干脆查一下它到底是什么来头吧~


1 Shannon: 2 额……我又获取到(${varName}的数据类型)了,刚写错了,写成了"${'varName'}".getClass().getName()里面的引号是多余的。我现在用 "${varName}".getClass().getName() 看到日志里打印了获取到的类型,是org.codehaus.groovy.runtime.GStringImpl 3 所以这个GStringImpl是个什么类型? 4 5 6 DeepSeek: 7 8 您观察得非常仔细,这个细节非常重要! 9 您看到的 `org.codehaus.groovy.runtime.GStringImpl` 是理解 Groovy 和 JMeter 如何协同工作的一个关键点。 10 11 # 🧠 GStringImpl 是什么? 12 13 `GStringImpl` 是 Groovy 语言中的一种特殊字符串类型,全称为 **Groovy String** 或 **GString**。 14 它与普通的 Java 字符串(`java.lang.String`)不同,最主要的特点是**支持动态求值**。 15 16 ## 🔍 核心区别:普通 String vs. GString 17 18 当您在 Groovy 中编写双引号字符串并包含 `${}` 插值表达式时, 19 Groovy 并不会立即将其转换为普通字符串,而是会创建一个 `GStringImpl` 对象。 20 21 * **`"Hello ${name}"`** → 这是一个 **GString** 22 * `'Hello ${name}'` (使用单引号) → 这是一个普通的 **String**,内容会原样显示为 `"Hello ${name}"` 23 * `"Hello world"` (没有插值) → 这通常会被优化为一个普通的 **String** 24 25 在您的 JMeter JSR223 脚本中,当您编写 `"${varName}"` 时,您正是在创建一个包含了 JMeter 变量引用的 GString。 26 27 ## ⚙️ GString 的工作原理:惰性求值 28 29 GString 的聪明之处在于它的“惰性求值”(Lazy Evaluation)机制。 30 它内部保存了两部分信息: 31 32 1. **字符串的常量部分** (如 `"Hello "`) 33 2. **需要动态求值的表达式列表** (如 `$name`) 34 35 只有当您真正需要这个字符串的值时(例如调用 `.toString()` 方法、打印它、或将它传递给一个需要 String 的方法),GString 才会执行插值,计算出最终的结果字符串。 36 37 在 JMeter 的上下文中,**这个“求值”的时刻,就是 `${varName}` 被替换为实际变量值的时刻**。 38 39 ## 📊 GString 与 String 的对比 40 41 | 特性 | **Java String (`java.lang.String`)** | **Groovy GString (`GStringImpl`)** | 42 | :--- | :--- | :--- | 43 | **定义方式** | 单引号 `'...'` 或 无插值的双引号 `"..."` | **包含 `${}` 插值的双引号 `"${var}..."`** | 44 | **内容** | 固定不变 | 包含**静态文本**和**动态表达式** | 45 | **求值时机** | 创建时即确定 | **使用时才求值** (惰性求值) | 46 | **在JMeter中的行为** | 是一个普通字符串 | 是一个**模板**,运行时会被渲染为最终字符串 | 47 48 ## 💡 在 JMeter 中的实践意义 49 50 理解这一点对于编写高效的 JSR223 脚本很重要: 51 52 1. **您看到的是“模板”,不是“值”**: 53 当您调用 `"${varName}".getClass().getName()` 时,您是在检查**模板本身**的类型,而不是变量 `varName` 的值的类型。 54 这就是为什么您会得到 `GStringImpl`。 55 56 2. **如何获取变量的实际值类型**: 57 要检查变量 `varName` **存储的值**的类型,正确的方法仍然是使用 `vars.get("varName")`: 58 ```groovy 59 // 正确:获取变量值的类型 60 def variableValue = vars.get("varName"); 61 log.info("变量值的实际类型是: " + variableValue.getClass().getName()); // 通常是 java.lang.String 62 63 // 对比:您之前的方法,获取的是GString模板的类型 64 log.info("GString模板的类型是: " + "${varName}".getClass().getName()); // 永远是 org.codehaus.groovy.runtime.GStringImpl 65 ``` 66 67 3. **性能考量**:GString 的惰性求值通常是高效的。 68 但如果您在性能关键的循环中反复将同一个 GString 转换为字符串(强制求值),可能会有一点开销。 69 在这种情况下,可以考虑提前将结果存入一个变量: 70 ```groovy 71 // 在循环外先求值并保存 72 String resolvedValue = "${someVar}"; 73 for (int i = 0; i < 1000; i++) { 74 // 使用 resolvedValue,而不是每次都解析 "${someVar}" 75 } 76 ``` 77 78 ## ✅ 总结 79 80 - **`GStringImpl` 是 Groovy 的一种智能字符串**,用于处理包含 `${}` 插值的字符串模板。 81 - 它的**惰性求值**特性使得 JMeter 变量能在运行时被正确替换。 82 - 要检查一个 **JMeter 变量的值的类型**,请使用 `vars.get("varName").getClass()`。 83 - 要检查一个**包含插值的字符串模板的类型**,您会得到 `GStringImpl`。 84 85 您通过实验捕捉到了这个细微差别,这是深入理解 JMeter 和 Groovy 如何协同工作的很好的一步!
这个,仅作了解,没事时慢慢看吧。先就当${varName}获取的变量的数据类型是某一种String类型吧~~~
② 两者的解析时机不同
来看第二个关键差异:vars.get()总能获取到 变量的最新值,但${}只能获取到变量的初始值。—— 确实存在这个问题,以后可要注意。虽然脚本中变量被赋予了新的值,调试取样器里也是新的值,但是JSR223脚本中获取到的确实还是最初的值。


但是,当我再增加一个JSR223组件,并在新的组件中获取变量值时,发现新的组件获取到最新的变量值了。看来,${}获取变量值不是一直都是初始值,这应该与它的解析机制有关,不过不影响上面使用「${}获取变量值时应该要谨慎」的结论。


好了,今天的主题,向下挖掘得够深入了,我们就此进入结论环节吧。
总结
这篇博客所记述的踩坑问题,基本暴露了一个人(确切地说是我自己…)在写脚本时没带脑子、粗心大意的事实,所以,能说啥,,,论把基础知识搞扎实和报错后积极调试的重要性——现在来看结论:
1、vars.get() 和 ${} 在获取变量时的写法,超基础、超重要—— vars.get('varName') / vars.get("varName"),${varName} / "${varName}"
1 def str = "123" 2 3 vars.put("str", str) 4 5 // 使用vars.get() 获取变量值 6 7 log.info ("使用vars.get() 获取变量值:" + vars.get("str") ) 8 9 10 // 使用 ${} 获取变量值 11 log.info ("使用${} 获取变量值:" + ${str} )
2、vars.get() 与 ${} 的关键差异
① vars.get("") 获取的变量的数据类型总是String,即使变量是通过vars.putObject()存储的其他类型。如果要获取原始类型,vars.putObject()和vars.getObject()才是最佳替代方案;而 ${} 获取的变量的数据类型,也是固定的,叫 org.codehaus.groovy.runtime.GStringImpl;
② 由于两者的解析机制不一致,vars.get("") 总能获取到变量最新的值,但 ${} 会存在不准确的情况。 因此,脚本中,推荐尽可能使用vars.get() 获取最新变量值;
3、 调试过程中的捕获的一些小猎物——
① Deep Seek 在指导程序时,也会有各种不正确的情况,要明确一个结论,只有亲自验证过才可靠。
② vars.get('varName').typeof 是一种错误的写法
③ 【用户定义的变量】组件中的定义的任何变量,其数据类型都是 java.lang.String


④ 在【if 控制器】组件的条件中,不能用 ${__jexl3("${flag}".instanceof Boolean, )} 这样的判断,会报错,看起来不支持这样的语法

⑤ vars.get("varName") ,引号在里面;"${varName}" ,引号可以在{}外面,但一定不能在{}里面,否则会报错。


注释掉圈红的一条代码就不会报错

而且,筒子,发现了没,那个${}只要在引号""里面,哪怕前后还有其他字符,它仍然会被解析!!! 就是上面的:通过 null 获取:
呛呛!~ 本篇博客就到这里啦!这周的博客没有按时(周日前)发布,但事实证明「有志者事竟成」,只要心意坚定,延期,也可以不用延很久!………再接再励,筒子们!~~~
下期预告
嗯,第四篇博客预告的那些关于字符串函数的小陷阱,下一篇咱终于可以一并处理下。。。
posted on 2025-09-08 20:25 Shannon_Zhang 阅读(37) 评论(0) 收藏 举报

浙公网安备 33010602011771号