JMeter JSR223预处理程序全攻略:用Groovy解锁复杂场景自动化

在JMeter性能测试中,面对动态签名、参数加密、数据格式转换等复杂业务场景,内置组件往往难以满足需求。而**JSR223预处理程序**(JSR223 PreProcessor)作为JMeter的"万能工具",支持通过Groovy、Java等脚本语言编写自定义逻辑,能轻松解决各类复杂参数处理问题。本文将从基础配置、核心用法到实战案例,全方位解析JSR223预处理程序,帮你彻底掌握这一高级技能。

一、JSR223预处理程序是什么?

JSR223是Java平台的脚本语言规范,JMeter基于该规范提供了JSR223系列组件(预处理程序、后置处理器、采样器等),其中**JSR223预处理程序**的核心作用是:在**取样器(如HTTP请求)执行前**,运行自定义脚本逻辑,完成参数生成、数据处理、请求组装等操作。

核心优势:

  1. 灵活性极强:支持Groovy、Python、JavaScript等多种脚本语言(JMeter 3.1+推荐优先使用Groovy,性能最优且兼容Java类库);

  2. 功能无上限:可调用Java类库、第三方Jar包,实现加密、签名、JSON/XML处理、数据库查询等任意复杂逻辑;

  3. 无缝集成JMeter :脚本中可直接操作JMeter变量(vars)、全局属性(props)、取样器配置等,与JMeter原生组件深度联动。

适用场景:

  • 接口请求参数需要动态加密(如MD5、SHA256、AES);

  • 生成复杂业务签名(如时间戳+密钥+参数拼接后加密);

  • 动态组装JSON/XML请求体(如根据条件添加不同字段);

  • 从数据库/缓存中查询测试数据并注入请求;

  • 批量修改请求头或参数(如统一添加Token、设备ID)。

二、基础配置:JSR223预处理程序入门

1. 组件添加与基本设置

  1. 在需要预处理的取样器(如HTTP请求)上右键 → 添加 → 前置处理器 → JSR223 PreProcessor;

  2. 核心配置项说明:

    1. Script Language :脚本语言,默认选择groovy(推荐,性能远超BeanShell、Python);

    2. Script File:外部脚本文件路径(适合复杂脚本,避免在界面直接编写);

    3. Script:脚本编辑区(简单脚本可直接在此编写);

    4. Parameters :传递给脚本的参数(脚本中通过args数组获取);

    5. Cache compiled script if available:缓存编译后的脚本(默认勾选,重复执行时提升性能,避免每次重新编译)。

2. 核心内置对象(必掌握)

JSR223脚本中可直接使用JMeter提供的内置对象,无需额外定义,核心对象如下:

|-----------|-----------------|---------------------------|------------------------------------------------------------|
| 对象名 | 类型 | 作用 | 示例 |
| vars | JMeterVariables | 操作当前线程组的局部变量(仅当前线程可见) | vars.put("key", "value")(存值)、vars.get("key")(取值) |
| props | Properties | 操作JMeter全局属性(所有线程组、线程共享) | props.put("globalKey", "value")props.get("globalKey") |
| ctx | JMeterContext | 获取JMeter上下文信息(如线程数、取样器信息) | ctx.getThreadNum()(获取当前线程号) |
| sampler | Sampler | 获取当前取样器配置(如HTTP请求的URL、参数) | sampler.getUrlString()(获取请求URL) |
| log | Logger | 输出日志到JMeter控制台/日志文件(调试用) | log.info("调试信息:" + vars.get("key")) |

3. 第一个Groovy脚本:生成动态参数

示例:生成随机手机号并存入JMeter变量,供HTTP请求使用。

脚本代码:

复制代码
Groovy 复制代码
// 生成11位随机手机号(以138开头)
def prefix = "138"
def suffix = new Random().nextInt(900000000) + 100000000  // 生成9位随机数
def phone = prefix + suffix.toString()

// 存入JMeter局部变量,取样器中可通过${phone}引用
vars.put("phone", phone)

// 日志输出调试(JMeter控制台可查看)
log.info("生成的随机手机号:" + phone)

使用方式:在HTTP请求参数中直接引用${phone},即可自动带入生成的手机号。

三、核心实战场景:JSR223预处理程序经典用法

场景1:接口签名生成(MD5/SHA256)

多数接口为防止请求篡改,会要求在请求参数中添加签名(如sign字段),签名规则通常为"参数排序+密钥+时间戳"拼接后加密。以下以"MD5签名"为例,演示完整实现。

签名规则:
  1. 请求参数包含appIdtimestampdata

  2. 按参数名ASCII升序排序(appIddatatimestamp);

  3. 拼接格式:appId=xxx&data=xxx&timestamp=xxx&secret=xxxsecret为接口密钥);

  4. 对拼接字符串做MD5加密(小写),作为sign参数。

JSR223脚本实现:
Groovy 复制代码
import java.security.MessageDigest
import java.util.TreeMap

// 1. 定义常量和基础参数
def appId = "test123"
def secret = "abcdefg123456"
def data = '{"userId":"1001","goodsId":"2002"}'  // 请求数据
def timestamp = System.currentTimeMillis() / 1000  // 秒级时间戳

// 2. 按参数名ASCII升序排序(TreeMap自动排序)
def params = new TreeMap()
params.put("appId", appId)
params.put("data", data)
params.put("timestamp", timestamp.toString())

// 3. 拼接参数字符串
def signSource = new StringBuilder()
params.each { key, value ->
    signSource.append(key).append("=").append(value).append("&")
}
signSource.append("secret=").append(secret)  // 拼接密钥
log.info("签名源字符串:" + signSource.toString())

// 4. MD5加密(小写)
def md5 = MessageDigest.getInstance("MD5")
byte[] md5Bytes = md5.digest(signSource.toString().getBytes("UTF-8"))
def sign = new BigInteger(1, md5Bytes).toString(16).padStart(32, '0')  // 补全32位

// 5. 存入JMeter变量,供取样器引用
vars.put("appId", appId)
vars.put("timestamp", timestamp.toString())
vars.put("data", data)
vars.put("sign", sign)

log.info("生成的签名:" + sign)
取样器引用:

在HTTP请求的"参数"或"请求体"中引用变量:

  • appId=${appId}

  • timestamp=${timestamp}

  • data=${data}

  • sign=${sign}

场景2:AES加密请求体(敏感数据加密)

对于手机号、身份证号等敏感数据,接口可能要求用AES加密后传输。以下示例将JSON请求体用AES-ECB模式加密(PKCS5填充)。

前置准备:

确保JMeter/lib目录下添加commons-codec-1.15.jar(用于Base64编码,可从Maven仓库下载)。

JSR223脚本实现:
Groovy 复制代码
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import org.apache.commons.codec.binary.Base64

// 1. 加密配置(与接口协商一致)
def secretKey = "1234567890abcdef"  // AES密钥(16位,ECB模式要求密钥长度16/24/32位)
def algorithm = "AES/ECB/PKCS5Padding"  // 算法/模式/填充方式

// 2. 原始请求体(敏感数据)
def originalBody = '{"phone":"13800138000","idCard":"110101199001011234"}'
log.info("原始请求体:" + originalBody)

// 3. AES加密实现
def encryptAES(String content, String key) {
    SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES")
    Cipher cipher = Cipher.getInstance(algorithm)
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec)
    byte[] encryptedBytes = cipher.doFinal(content.getBytes("UTF-8"))
    return Base64.encodeBase64String(encryptedBytes)  // 加密后Base64编码(方便传输)
}

// 4. 执行加密并存入变量
def encryptedBody = encryptAES(originalBody, secretKey)
vars.put("encryptedBody", encryptedBody)
log.info("AES加密后请求体(Base64):" + encryptedBody)
取样器引用:

在HTTP请求的"请求体数据"中直接引用${encryptedBody},接口接收后解密即可。

场景3:动态组装JSON请求体(根据条件添加字段)

部分接口的请求体字段需根据业务条件动态增减(如VIP用户显示vipLevel字段,普通用户不显示),可通过JSR223脚本灵活组装。

JSR223脚本实现:
Groovy 复制代码
import groovy.json.JsonBuilder

// 1. 定义基础字段
def userId = vars.get("userId")  // 从CSV或其他变量获取用户ID
def userName = vars.get("userName")
def userType = vars.get("userType")  // 用户类型:VIP/COMMON

// 2. 动态构建JSON(Groovy的JsonBuilder简化JSON组装)
def json = new JsonBuilder()
json {
    userId(userId)
    userName(userName)
    createTime(new Date().format("yyyy-MM-dd HH:mm:ss"))  // 当前时间
    
    // 条件字段:仅VIP用户添加vipLevel
    if (userType == "VIP") {
        vipLevel(3)  // VIP等级
        discount(0.8)  // 折扣率
    } else {
        discount(1.0)  // 普通用户无折扣
    }
}

// 3. 转为JSON字符串并存入变量
def requestBody = json.toPrettyString()  // 格式化JSON(可选)
vars.put("requestBody", requestBody)

log.info("动态组装的请求体:" + requestBody)
取样器引用:

HTTP请求体直接填入${requestBody},最终效果:

  • VIP用户:包含vipLeveldiscount=0.8

  • 普通用户:无vipLeveldiscount=1.0

场景4:从数据库查询测试数据(JDBC联动)

压测时需从数据库获取真实数据(如已存在的用户ID),可通过JSR223预处理程序调用JDBC查询数据并注入请求。

前置准备:
  1. 添加"JDBC Connection Configuration"组件,配置数据库连接(URL、用户名、密码、驱动类);

  2. 确保JMeter/lib目录下添加对应数据库驱动Jar包(如MySQL的mysql-connector-java-8.0.30.jar)。

JSR223脚本实现:
Groovy 复制代码
import org.apache.jmeter.protocol.jdbc.config.DataSourceElement
import java.sql.Connection
import java.sql.Statement
import java.sql.ResultSet

// 1. 获取JDBC连接池(与JDBC Connection Configuration的Variable Name一致)
def dataSource = DataSourceElement.getDataSource("db_conn")  // Variable Name=db_conn
Connection conn = dataSource.getConnection()

try {
    // 2. 执行SQL查询(查询前100个用户ID,随机取一个)
    Statement stmt = conn.createStatement()
    String sql = "SELECT user_id FROM t_user LIMIT 100"
    ResultSet rs = stmt.executeQuery(sql)
    
    // 3. 收集查询结果
    def userIds = []
    while (rs.next()) {
        userIds.add(rs.getString("user_id"))
    }
    
    // 4. 随机取一个用户ID存入变量
    if (userIds.size() > 0) {
        def randomUserId = userIds.get(new Random().nextInt(userIds.size()))
        vars.put("randomUserId", randomUserId)
        log.info("从数据库获取的随机用户ID:" + randomUserId)
    } else {
        log.error("未查询到用户ID")
        vars.put("randomUserId", "default_1001")  // 默认值
    }
} catch (Exception e) {
    log.error("数据库查询异常:" + e.getMessage())
    vars.put("randomUserId", "default_1001")
} finally {
    // 5. 关闭连接(释放资源)
    if (conn != null) {
        conn.close()
    }
}
取样器引用:

在HTTP请求中引用${randomUserId},即可动态注入从数据库查询的用户ID。

四、性能优化与避坑指南

1. 性能优化技巧

JSR223脚本的性能直接影响压测结果的准确性,尤其是高并发场景,需注意以下优化点:

  • 优先使用Groovy语言:Groovy是JMeter优化后的首选脚本语言,性能比BeanShell快10倍以上,且兼容Java类库;

  • 勾选"Cache compiled script if available":缓存编译后的脚本,避免每次执行都重新编译(高并发场景必备);

  • 避免在脚本中创建大量对象 :如循环中重复创建RandomMessageDigest等对象,可提前初始化复用;

  • 复杂脚本用外部文件 :将大型脚本保存为.groovy文件,通过"Script File"导入(便于维护和复用,且编译效率更高);

  • 禁用不必要的 日志 :高并发时log.info()会消耗大量资源,调试完成后注释或删除日志输出。

2. 常见问题与解决方案

问题1:脚本报错"ClassNotFoundException"(如找不到MessageDigest、Base64)
  • 原因:缺少相关类库或Jar包;

  • 解决方案:

    • 确认脚本中导入的类是否正确(如java.security.MessageDigest是Java内置类,无需额外Jar包);

    • 第三方类库(如Base64)需将Jar包放入JMeter/lib目录,重启JMeter生效。

问题2:变量引用失败(${key}无法获取脚本中存入的值)
  • 原因:

    • 脚本中变量名拼写错误;

    • props存储全局变量,但线程组执行顺序导致变量未初始化;

    • 脚本执行异常,变量未成功存入;

  • 解决方案:

    • log.info(vars.get("key"))调试变量是否存入;

    • 局部变量用vars,跨线程组变量用props,且确保存储变量的线程组先执行;

    • 捕获脚本异常,避免因异常导致变量未赋值。

问题3:高并发场景下脚本执行缓慢,导致压测结果失真
  • 原因:脚本逻辑复杂(如循环次数过多、频繁数据库查询)或未优化;

  • 解决方案:

    • 简化脚本逻辑,将非必要操作移出预处理程序;

    • 数据库查询等耗时操作改为提前批量导入CSV,脚本从CSV读取数据;

    • 调整JMeter堆内存(jmeter.bat/jmeter中的HEAP参数),避免内存溢出。

问题4:Groovy脚本语法报错(如"Unexpected token")
  • 原因:Groovy语法与Java略有差异(如字符串拼接、闭包用法);

  • 解决方案:

    • 遵循Groovy语法规范(如字符串可使用单引号/双引号,多行字符串用''');

    • 复杂逻辑可分步骤编写,用log.info()调试每一步结果;

    • 参考Groovy官方文档或JMeter官方示例脚本。

五、总结:JSR223预处理程序的核心价值

JSR223预处理程序的核心价值在于**打破JMeter内置组件的功能限制**,通过脚本语言实现任意复杂的参数处理逻辑。无论是动态签名、数据加密、JSON组装,还是数据库联动,它都能轻松应对,是性能测试工程师处理复杂业务场景的"必备工具"。

使用时需注意:

  1. 优先选择Groovy语言,兼顾性能和兼容性;

  2. 熟练掌握内置对象(varspropslog),实现与JMeter的深度联动;

  3. 高并发场景下需优化脚本性能,避免成为压测瓶颈;

  4. 脚本中添加异常捕获和日志输出,便于调试和问题定位。

通过本文的基础配置、实战场景和避坑指南,

相关推荐
Jinkxs1 天前
LoadBalancer- 主流负载均衡工具盘点:Nginx / Haproxy / Keepalived 基础介绍
运维·nginx·负载均衡
CQU_JIAKE1 天前
4.28~4.30【Q】
linux·运维·服务器
先知后行。1 天前
Linux 设备模型和platform平台
linux·运维·服务器
掌心向暖RPA自动化1 天前
如何获取网页某个元素在屏幕可见部分的中心坐标影刀RPA懒加载坐标定位技巧
java·javascript·自动化·rpa·影刀rpa
雪碧聊技术1 天前
什么是压力测试?压力测试的工具有哪些?一文详解
jmeter·压力测试·wrk
leaves falling1 天前
Linux 基础指令完全指南 —— 从入门到熟练
linux·运维·服务器
charlie1145141911 天前
嵌入式Linux驱动开发——新字符设备驱动 API 概览
linux·运维·驱动开发
架构源启1 天前
OpenClaw 只能手动写脚本?我用 Chrome 插件实现了“录制即生成“
前端·人工智能·chrome·自动化
DFT计算杂谈1 天前
VASP官方教程 TRIQS DFT+DMFT计算教程
运维·css·自动化·html·css3
2301_803554521 天前
Linux里面的文件描述符和windows里面的句柄
linux·运维·服务器