JMeter 二次开发教程
目录
- 为什么进行 JMeter 二次开发?
- 开发环境准备
- JMeter 核心架构与扩展点
- 第一个插件:自定义取样器
- 第二个插件:自定义后端监听器
- 第三个插件:自定义参数化元件
- 高级主题
- 调试与打包发布
- 总结与资源
为什么进行 JMeter 二次开发?
JMeter 本身已经非常强大,覆盖了绝大多数性能测试场景,但在某些特定情况下,我们需要进行二次开发:

- 协议支持:JMeter 不支持某个私有或新兴的协议(如公司内部的 RPC 协议、专有的游戏协议等)。
- 复杂业务逻辑:测试场景包含非常复杂的、无法通过现有元件组合实现的业务流程。
- 深度集成:需要将 JMeter 与公司内部的 CI/CD 系统、监控系统、数据平台等进行深度定制集成。
- 性能优化:对 JMeter 某个元件的性能瓶颈进行优化。
- 增强报告:需要生成更符合公司业务需求的定制化测试报告。
通过二次开发,你可以将 JMeter 打造成一个完全贴合自身业务需求的、高性能的、自动化的测试平台。
开发环境准备
1 必需软件
-
JDK:JMeter 是基于 Java 开发的,你需要安装 JDK 8 或更高版本,建议使用 JDK 8,因为它最稳定且社区支持最广。
- 下载地址:Oracle JDK 或 OpenJDK
- 配置环境变量
JAVA_HOME。
-
Apache JMeter:下载 JMeter 的源码包(
.zip或.tar.gz),不要下载二进制发行版,因为我们需要源码。- 下载地址:JMeter 官网
- 解压到你喜欢的目录,
D:\dev\apache-jmeter-5.6.2。
-
IDE (集成开发环境):强烈推荐 IntelliJ IDEA(社区版即可)或 Eclipse,IDEA 对 Maven/Gradle 的支持更好,代码提示和调试功能更强大。
(图片来源网络,侵删) -
构建工具:JMeter 源码使用 Maven 进行管理,如果你的 IDE(如 IDEA)自带 Maven,则无需额外安装。
2 IDE 配置(以 IntelliJ IDEA 为例)
-
打开 JMeter 源码:
- 启动 IDEA。
File->Open,选择你解压的 JMeter 源码目录(D:\dev\apache-jmeter-5.6.2)。- IDEA 会自动识别这是一个 Maven 项目,并开始下载依赖,这可能需要一些时间。
-
设置项目 JDK:
- 进入
File->Project Structure->Project。 - 确保
Project SDK选择你安装的 JDK 版本。
- 进入
-
熟悉项目结构:
src/core: JMeter 核心代码,包含引擎、线程、计划等。src/components: 标准元件,如 HTTP 请求、CSV 数据集、监听器等。src/protocol: 协议支持包,如 HTTP, FTP, JDBC 等。src/functions: 内置函数。lib: 所有依赖的 Jar 包。
JMeter 核心架构与扩展点
理解 JMeter 的架构是二次开发的关键,JMeter 的核心是“插件化”架构,几乎所有功能都是通过“元件”来实现的。
1 核心概念
- JMeter Test Plan (测试计划):测试的顶层容器。
- Thread Group (线程组):虚拟用户的集合,定义了测试的并发数、循环次数等。
- Samplers (取样器):执行实际工作的元件,如 HTTP 请求、JDBC 请求等,它们是向目标服务器发送请求并接收响应的“动作”。
- Listeners (监听器):收集测试结果的元件,如查看结果树、聚合报告等,它们是展示和记录数据的“窗口”。
- Timers (定时器):在请求之间添加延迟,模拟用户思考时间。
- Assertions (断言):检查服务器响应是否符合预期,用于判断请求是否成功。
- Config Elements (配置元件):配置测试数据,如 HTTP 请求默认值、CSV 数据集等。
- Pre/Post Processors (前置/后置处理器):在取样器执行前或执行后进行特殊处理的元件。
2 核心扩展点
JMeter 通过 Java 接口来定义这些元件,我们的二次开发就是实现这些接口。
| 扩展点 | 接口路径 | 作用 | 示例 |
|---|---|---|---|
| 自定义取样器 | org.apache.jmeter.samplers.Sampler |
实现一个自定义的请求逻辑,这是最常见的扩展点。 | 自定义 RPC 取样器、Dubbo 取样器 |
| 自定义监听器 | org.apache.jmeter.visualizers.Visualizerorg.apache.jmeter.reporters.AbstractListenerElement |
创建一个新的结果展示面板或生成自定义报告。 | 将结果实时写入 Elasticsearch、生成自定义图表 |
| 自定义后端监听器 | org.apache.jmeter.reporters.AbstractBackendListenerClient |
异步发送测试数据到外部系统,不影响 JMeter 主线程性能。 | 异步将数据发送到 InfluxDB、Prometheus、Kafka |
| 自定义配置元件 | org.apache.jmeter.config.ConfigTestElement |
提供可复用的配置信息。 | 自定义数据库连接池配置、加密数据配置 |
| 自定义函数 | org.apache.jmeter.functions.Function |
创建可在任何地方使用的 ${__myFunc(...)} 函数。 |
生成 UUID、从数据库获取随机值 |
| 自定义断言 | org.apache.jmeter.assertions.Assertion |
进行更复杂的校验。 | 校验 JSON 响应中的某个字段值 |
第一个插件:自定义取样器
我们将创建一个最简单的自定义取样器,它在控制台打印一条消息,并模拟一个固定的响应。
1 创建 Maven 子模块
为了不污染 JMeter 源码,最佳实践是创建一个独立的 Maven 模块。
-
在 JMeter 根目录下,创建一个
pom.xml文件,内容如下:<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>jmeter-custom-sampler</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- 引入 JMeter 核心依赖 --> <dependency> <groupId>org.apache.jmeter</groupId> <artifactId>ApacheJMeter_core</artifactId> <version>5.6.2</version> <!-- 与你的 JMeter 版本保持一致 --> </dependency> </dependencies> <build> <plugins> <!-- Maven 编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project> -
在 IDEA 中,右键点击
jmeter-custom-sampler目录,选择Add as Maven Project。
2 实现 Sampler 接口
在 src/main/java/com/example/jmeter 包下,创建 HelloWorldSampler.java。
package com.example.jmeter;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestElement;
public class HelloWorldSampler extends AbstractSampler {
// 用于在 JMeter GUI 中显示的名称
private static final String STATIC_LABEL = "Hello World Sampler";
// 自定义参数的名称,这些参数会显示在 GUI 的“参数”列表中
private static final String MY_MESSAGE = "myMessage";
public HelloWorldSampler() {
}
@Override
public String getStaticLabel() {
return STATIC_LABEL;
}
@Override
public SampleResult sample(Entry entry) {
// 1. 创建 SampleResult 对象,用于存储本次采样的结果
SampleResult result = new SampleResult();
result.setSampleLabel(getName()); // 使用测试计划中给取样器起的名字
result.sampleStart(); // 开始计时
try {
// 2. 获取用户在 GUI 中输入的参数
String message = getPropertyAsString(MY_MESSAGE, "Default Hello Message!");
// 3. 模拟业务逻辑
System.out.println("[HelloWorldSampler] Executing with message: " + message);
// 4. 设置响应数据(可以是文本、JSON、XML 等)
String responseData = "Server received your message: " + message;
result.setResponseData(responseData, null); // 第二个参数是编码
// 5. 设置响应码和状态
result.setResponseCodeOK();
result.setResponseMessage("OK");
// 6. 设置采样成功
result.setSuccessful(true);
} catch (Exception e) {
// 7. 如果发生异常,记录失败信息
result.setSuccessful(false);
result.setResponseCode("500");
result.setResponseMessage("An error occurred: " + e.getMessage());
result.setResponseData(e.toString(), null);
} finally {
result.sampleEnd(); // 结束计时
}
return result;
}
// --- 以下是 GUI 相关方法 ---
@Override
public String[] getArgumentNames() {
return new String[]{MY_MESSAGE};
}
public String getMyMessage() {
return getPropertyAsString(MY_MESSAGE);
}
public void setMyMessage(String message) {
setProperty(MY_MESSAGE, message);
}
}
3 创建 GUI 面板
为了让用户能在 JMeter 中配置我们的取样器,我们需要创建一个 GUI 组件。
在 src/main/java/com/example/jmeter 包下,创建 HelloWorldSamplerGui.java。
package com.example.jmeter;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;
import javax.swing.*;
import java.awt.*;
public class HelloWorldSamplerGui extends AbstractSamplerGui {
private JTextField messageField;
public HelloWorldSamplerGui() {
init();
}
@Override
public String getLabelResource() {
return this.getStaticLabel(); // 返回和 Sampler 一样的标签名
}
@Override
public String getStaticLabel() {
return "Hello World Sampler"; // 在 JMeter 中显示的名称
}
private void init() {
setLayout(new BorderLayout(0, 5));
setBorder(makeBorder());
// 创建面板
JPanel mainPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 0, 5, 0);
gbc.fill = GridBagConstraints.HORIZONTAL;
// 添加参数输入行
gbc.gridx = 0;
gbc.gridy = 0;
mainPanel.add(new JLabel("Message to send:"), gbc);
gbc.gridx = 1;
messageField = new JTextField(20);
mainPanel.add(messageField, gbc);
add(mainPanel, BorderLayout.NORTH);
add(createVerticalSpacer(), BorderLayout.CENTER);
}
@Override
public void configure(TestElement element) {
super.configure(element);
messageField.setText(element.getPropertyAsString(HelloWorldSampler.MY_MESSAGE));
}
@Override
public TestElement createTestElement() {
HelloWorldSampler sampler = new HelloWorldSampler();
modifyTestElement(sampler);
return sampler;
}
@Override
public void modifyTestElement(TestElement element) {
super.configureTestElement(element);
((HelloWorldSampler) element).setMyMessage(messageField.getText());
}
@Override
public void clearGui() {
super.clearGui();
messageField.setText("");
}
}
4 注册插件
JMeter 需要通过一个 properties 文件来发现我们的插件。
在 src/main/resources 目录下,创建文件 org.apache.jmeter.config.plugininterfaces.properties。
**
# Custom Sampler com.example.jmeter.HelloWorldSampler=com.example.jmeter.HelloWorldSamplerGui
- Key:
包名.类名 - Value:
包名.GUI类名
5 编译和测试
-
编译插件:在 IDEA 的 Maven 工具窗口中,执行
jmeter-custom-sampler->Lifecycle->compile,成功后,target目录下会生成jmeter-custom-sampler-1.0-SNAPSHOT.jar。 -
部署到 JMeter:
- 将生成的
jmeter-custom-sampler-1.0-SNAPSHOT.jar复制到你的 JMeter 安装目录的lib/ext文件夹下。 - 重启 JMeter。
- 将生成的
-
在 JMeter 中使用:
- 启动 JMeter。
- 在一个测试计划下,右键点击“线程组”,选择“添加” -> “取样器” -> “Hello World Sampler”。
- 你会看到一个新的取样器,可以在其中输入消息。
- 添加一个“查看结果树”监听器,然后运行测试。
- 查看控制台输出,你会看到打印的消息,在“查看结果树”中,你也能看到返回的响应数据。
第二个插件:自定义后端监听器
后端监听器是异步发送数据的,性能开销极小,非常适合与监控系统(如 Prometheus, InfluxDB)集成。
我们将创建一个简单的后端监听器,将每个请求的摘要信息打印到控制台。
1 实现 AbstractBackendListenerClient
package com.example.jmeter;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleListener;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.backend.BackendListener;
import org.apache.jmeter.visualizers.backend.BackendListenerClient;
import org.apache.jmeter.visualizers.backend.BackendListenerContext;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class MyBackendListener extends BackendListener implements BackendListenerClient, SampleListener {
// 用于测试的计数器
private static final AtomicInteger counter = new AtomicInteger(0);
@Override
public void setupTest(BackendListenerContext context) throws Exception {
System.out.println("[MyBackendListener] Setup test started.");
// 在这里可以初始化连接,如创建数据库连接、HTTP 客户端等
}
@Override
public void teardownTest(BackendListenerContext context) throws Exception {
System.out.println("[MyBackendListener] Teardown test started.");
// 在这里可以关闭资源,如关闭数据库连接
}
@Override
public void handleSampleResults(List<SampleResult> sampleResults, BackendListenerContext context) {
// 这个方法会被 JMeter 的后台线程池调用,处理一批样本结果
for (SampleResult result : sampleResults) {
// 在这里处理每个 SampleResult
// 发送到 Elasticsearch、Kafka 或数据库
String log = String.format(
"Thread: %s, Label: %s, Elapsed: %d ms, Success: %b",
result.getThreadName(),
result.getSampleLabel(),
result.getTime(),
result.isSuccessful()
);
System.out.println("[MyBackendListener] " + log);
}
}
// --- SampleListener methods (optional, if you want to receive samples one by one) ---
// handleSampleResults 已经足够,实现这些方法是为了兼容性
@Override
public void sampleStarted(SampleEvent e) {}
@Override
public void sampleStopped(SampleEvent e) {}
}
2 创建 GUI 面板
后端监听器的 GUI 相对简单,通常只需要一些配置参数。
package com.example.jmeter;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.visualizers.gui.AbstractBackendListenerGui;
import javax.swing.*;
public class MyBackendListenerGui extends AbstractBackendListenerGui {
public MyBackendListenerGui() {
super();
}
@Override
public String getLabelResource() {
return this.getStaticLabel();
}
@Override
public String getStaticLabel() {
return "My Custom Backend Listener";
}
@Override
public TestElement createTestElement() {
MyBackendListener listener = new MyBackendListener();
modifyTestElement(listener);
return listener;
}
@Override
public void modifyTestElement(TestElement element) {
super.configureTestElement(element);
}
@Override
public void clearGui() {
super.clearGui();
}
}
3 注册插件
在 src/main/resources 目录下,创建或修改 org.apache.jmeter.visualizers.backend.BackendListenerClient.properties 文件。
**
# Custom Backend Listener com.example.jmeter.MyBackendListener=com.example.jmeter.MyBackendListenerGui
4 编译、部署和测试
步骤与自定义取样器完全相同:
- 编译 Maven 项目。
- 将生成的 Jar 包复制到 JMeter 的
lib/ext目录。 - 重启 JMeter。
- 在测试计划中添加“监听器” -> “My Custom Backend Listener”。
- 运行测试,查看 JMeter 控制台的输出,你会看到来自后端监听器的日志。
第三个插件:自定义参数化元件
配置元件用于提供可复用的数据,我们将创建一个简单的配置元件,它提供一个随机数。
1 实现 ConfigTestElement
package com.example.jmeter;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.testelement.TestElement;
import java.util.Random;
public class RandomNumberConfig extends ConfigTestElement {
private static final String STATIC_LABEL = "Random Number Provider";
private static final String MIN_VALUE = "minValue";
private static final String MAX_VALUE = "maxValue";
public RandomNumberConfig() {
}
@Override
public String getStaticLabel() {
return STATIC_LABEL;
}
// 这个方法会被 JMeter 在取样器执行前调用
@Override
public void addTestElement(TestElement child) {
super.addTestElement(child);
// child 是一个取样器,我们可以在这里为它设置参数
if (child instanceof org.apache.jmeter.samplers.Sampler) {
org.apache.jmeter.samplers.Sampler sampler = (org.apache.jmeter.samplers.Sampler) child;
int min = getPropertyAsInt(MIN_VALUE, 1);
int max = getPropertyAsInt(MAX_VALUE, 100);
int randomNum = min + new Random().nextInt((max - min) + 1);
// 将随机数作为一个 JMeter 属性传递给取样器
// 取样器可以通过 JMeterContextService.getContext().getCurrentSampler().getThreadContext().getJMeterProperties() 来获取
// 但更常见的做法是通过函数或变量
// 这里我们通过设置一个线程变量来实现
JMeterContextService.getContext().getVariables().put("RANDOM_NUM", String.valueOf(randomNum));
}
}
// Getter and Setter for GUI
public int getMinValue() {
return getPropertyAsInt(MIN_VALUE, 1);
}
public void setMinValue(int value) {
setProperty(MIN_VALUE, value);
}
public int getMaxValue() {
return getPropertyAsInt(MAX_VALUE, 100);
}
public void setMaxValue(int value) {
setProperty(MAX_VALUE, value);
}
}
2 创建 GUI 面板
package com.example.jmeter;
import org.apache.jmeter.config.gui.AbstractConfigGui;
import org.apache.jmeter.testelement.TestElement;
import javax.swing.*;
import java.awt.*;
public class RandomNumberConfigGui extends AbstractConfigGui {
private JSpinner minSpinner;
private JSpinner maxSpinner;
public RandomNumberConfigGui() {
init();
}
@Override
public String getLabelResource() {
return this.getStaticLabel();
}
@Override
public String getStaticLabel() {
return "Random Number Provider";
}
private void init() {
setLayout(new BorderLayout(0, 5));
setBorder(makeBorder());
JPanel mainPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 0, 5, 0);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
mainPanel.add(new JLabel("Min Value:"), gbc);
gbc.gridx = 1;
minSpinner = new JSpinner(new SpinnerNumberModel(1, 0, Integer.MAX_VALUE, 1));
mainPanel.add(minSpinner, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
mainPanel.add(new JLabel("Max Value:"), gbc);
gbc.gridx = 1;
maxSpinner = new JSpinner(new SpinnerNumberModel(100, 0, Integer.MAX_VALUE, 1));
mainPanel.add(maxSpinner, gbc);
add(mainPanel, BorderLayout.NORTH);
}
@Override
public void configure(TestElement element) {
super.configure(element);
minSpinner.setValue(element.getPropertyAsInt(RandomNumberConfig.MIN_VALUE));
maxSpinner.setValue(element.getPropertyAsInt(RandomNumberConfig.MAX_VALUE));
}
@Override
public TestElement createTestElement() {
RandomNumberConfig config = new RandomNumberConfig();
modifyTestElement(config);
return config;
}
@Override
public void modifyTestElement(TestElement element) {
super.configureTestElement(element);
((RandomNumberConfig) element).setMinValue((Integer) minSpinner.getValue());
((RandomNumberConfig) element).setMaxValue((Integer) maxSpinner.getValue());
}
@Override
public void clearGui() {
super.clearGui();
minSpinner.setValue(1);
maxSpinner.setValue(100);
}
}
3 注册插件
在 src/main/resources 目录下,创建 org.apache.jmeter.config.plugininterfaces.properties 文件。
**
# Custom Config Element com.example.jmeter.RandomNumberConfig=com.example.jmeter.RandomNumberConfigGui
注意:如果文件已存在(因为第一个示例),请追加内容,不要覆盖。
4 编译、部署和测试
- 编译并部署 Jar 包。
- 重启 JMeter。
- 在测试计划中,添加“配置元件” -> “Random Number Provider”。
- 在一个取样器(如 HTTP 请求)中,使用变量
${__P(RANDOM_NUM)}或直接${RANDOM_NUM}来引用这个随机数。 - 添加一个“调试取样器”和一个“查看结果树”,运行测试,你可以在“查看结果树”的“请求”选项卡中看到请求参数里包含了生成的随机数。
高级主题
- 国际化:通过创建
messages.properties文件,可以为你的插件添加多语言支持。 - 使用 Groovy:对于一些轻量级的逻辑,你可以通过 JSR223 采样器使用 Groovy 脚本,这比开发一个完整的 Java 插件要快得多。
- 与 CI/CD 集成:将你的插件 Jar 包作为项目的一部分,通过 Maven/Gradle 构建流程,自动部署到测试环境。
- 性能分析:使用 JProfiler、YourKit 等工具分析你的插件代码,找出性能瓶颈。
- 测试你的插件:为你的插件编写 JUnit 测试用例,确保其逻辑正确。
调试与打包发布
1 调试
- 远程调试:这是最强大的调试方式。
- 在你的插件代码中设置断点。
- 在 IDEA 中,
Run->Edit Configurations->Remote。 - 设置 Host 为
localhost,Port 为5005(或任意未被占用的端口)。 - 启动这个 Remote 配置。
- 在 JMeter 的
bin目录下,编辑jmeter.properties文件,找到server.rmi.localport和server.rmi.port,确保它们不冲突。 - 最重要的一步:修改 JMeter 启动脚本 (
jmeter.bat或jmeter.sh),在 Java 启动命令中添加以下参数:-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
suspend=y会让 JVM 等待调试器连接,suspend=n则会立即启动。 - 启动 JMeter,IDEA 会自动连接到 JMeter 进程,当执行到断点时,IDEA 就会暂停。
2 打包发布
- 打包:在 Maven 中,执行
package命令,会生成一个.jar文件。 - 发布到 Maven 仓库:如果你们公司有自己的 Maven 仓库(如 Nexus、Artifactory),可以配置
pom.xml,执行deploy命令,将插件发布到仓库,方便其他项目引用。 - 发布到 JMeter 插件市场:如果你的插件非常有用,可以向 JMeter 官方社区提交,成为官方插件。
总结与资源
JMeter 二次开发是一个将通用工具变为专用利器的强大过程,本教程涵盖了从零开始创建自定义取样器、后端监听器和配置元件的全过程。
核心要点回顾
- 环境:JDK + JMeter 源码 + IDEA + Maven。
- 架构:理解 JMeter 的插件化架构和核心元件。
- 实现:根据需求实现对应的接口(
Sampler,BackendListenerClient等)。 - GUI:为你的元件创建一个友好的配置界面。
- 注册:通过
properties文件让 JMeter 发现你的插件。 - 测试:编译、部署、重启、验证。
推荐资源
- 官方文档:
- JMeter Extending JMeter:这是最权威的文档,必读!
- JMeter Javadoc:所有接口和类的官方文档。
- 社区与示例:
- JMeter GitHub:查看现有插件的源码是最好的学习方式。
- 搜索 GitHub 上关于 "JMeter custom plugin" 的项目,有很多优秀的开源示例。
- 书籍:
《JMeter in Action》:这本书有专门的章节讲解 JMeter 开发。
希望这份详细的教程能帮助你顺利开启 JMeter 二次开发之旅!
