shannonredeemed

vars.get() 与 ${}引用符在获取变量值时的注意事项

前言

  vars.get("") 或 ${} 引用符都可以用来获取Jmeter中变量值,但两者是有差别的。除了使用场景的限制,当脚本结构或内容有点复杂(此时脑子容易不清醒的情况下),在使用vars.get("") 或 ${} 引用符获取变量值时,就需要留心了。

踩坑之  当vars.get("变量名") 被写成了vars.get("${变量名}"),就是用力过度,就获取不到预期的变量值了呀

1、上配置

(1)线程组,单用户多次循环

image

(2)使用计数器变量 - caseCount

image

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

image

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

image

2、看运行情况

  程序没有报错,但是caseCount的值没获取到,直接是null了。。。

image

image

3、分析请教

(1) 问题分析与排查

  啥也别说了,先配上 调试取样器,看看是啥情况

image

image

image

image

  就是null啊,还有啥好犹豫的。。。。。。对着屏幕和代码,静心地琢磨一会儿……竟然发现了细节中的魔鬼……我这个变量caseCount是怎么获取的啊。。。。

4、调试与解决

(1)说干就干,调整一下

image

 image

 image

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

image

 image

image

(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类型,也就是变量实际存储的类型。来验证一下吧。 

image

image

  大惊失色!测试的结果是,vars.get()获取的变量,类型总是String,而不是如上面说的,能够获取到变量本来的数据类型。。。。这个结论告诉我们。。。。Deep Seek的答案,也会出错!!! 实践是检验真理的唯一标准。。。!!!

  再继续测试,看一下${'varName'}的数据类型是什么,结论也出来了—— org.codehaus.groovy.runtime.GStringImpl ——实在是一张有点新的面孔下,干脆查一下它到底是什么来头吧~ 

image

 image

 

 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脚本中获取到的确实还是最初的值。

image

image

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

image

 

image

   好了,今天的主题,向下挖掘得够深入了,我们就此进入结论环节吧。

 

总结

  这篇博客所记述的踩坑问题,基本暴露了一个人(确切地说是我自己…)在写脚本时没带脑子、粗心大意的事实,所以,能说啥,,,论把基础知识搞扎实和报错后积极调试的重要性——现在来看结论:

  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

image

 image

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

image

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

image

image

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

image

   而且,筒子,发现了没,那个${}只要在引号""里面,哪怕前后还有其他字符,它仍然会被解析!!! 就是上面的:通过 null 获取:

  呛呛!~ 本篇博客就到这里啦!这周的博客没有按时(周日前)发布,但事实证明「有志者事竟成」,只要心意坚定,延期,也可以不用延很久!………再接再励,筒子们!~~~

下期预告

  嗯,第四篇博客预告的那些关于字符串函数的小陷阱,下一篇咱终于可以一并处理下。。。

posted on 2025-09-08 20:25  Shannon_Zhang  阅读(37)  评论(0)    收藏  举报