1. 简介
监控是排查问题、优化性能的利器。 我们当然可以手动在代码中插入计时逻辑和日志,但这样会带来大量重复且干扰业务逻辑的样板代码,非常不优雅。
更聪明的做法是借助监控框架,通过注解方式自动完成埋点。比如本文要介绍的 Dropwizard Metrics 就是一个经典选择。
本篇将使用 Metrics AspectJ 结合 Dropwizard 的 @Timed
注解,为普通 Java 类实现方法级别的性能监控。✅
无需 Spring 等重量级框架,简单粗暴地通过 AOP 编译期织入实现。
2. Maven 依赖配置
首先引入核心依赖。Metrics AspectJ 分为两个模块:
<dependency>
<groupId>io.astefanutti.metrics.aspectj</groupId>
<artifactId>metrics-aspectj</artifactId>
<version>1.2.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.astefanutti.metrics.aspectj</groupId>
<artifactId>metrics-aspectj-deps</artifactId>
<version>1.2.0</version>
</dependency>
metrics-aspectj
:提供基于 AspectJ 的注解支持(如@Timed
)metrics-aspectj-deps
:包含 Metrics 核心库等必要依赖
⚠️ 注意我们排除了 slf4j-api,避免与项目已有日志框架冲突。
接着配置 aspectj-maven-plugin
,启用编译期织入(Compile-time weaving):
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<aspectLibraries>
<aspectLibrary>
<groupId>io.astefanutti.metrics.aspectj</groupId>
<artifactId>metrics-aspectj</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
关键点:
- ✅ 必须指定
<aspectLibraries>
,让插件识别@Timed
等切面逻辑 - ✅ 使用
compile
goal 实现编译期织入,性能无 runtime 开销 - ❌ 如果只用 IDE 运行(未走 Maven 编译),切面不会生效,监控数据为空
3. 注解埋点与示例
3.1 被监控的类
定义一个普通类 ObjectRunner
,使用 @Metrics
和 @Timed
添加监控:
import com.codahale.metrics.annotation.Timed;
import io.astefanutti.metrics.aspectj.Metrics;
@Metrics(registry = "objectRunnerRegistryName")
public class ObjectRunner {
@Timed(name = "timerName")
public void run() throws InterruptedException {
Thread.sleep(1000L);
}
}
要点说明:
@Metrics
标注类级别,声明该类需要被 Metrics AspectJ 处理registry
属性指定注册表名称,用于后续获取指标数据@Timed
标注方法,自动生成一个名为timerName
的计时器- 方法内部模拟耗时操作(sleep 1秒)
3.2 启动类与指标输出
public class ApplicationMain {
static final MetricRegistry registry = new MetricRegistry();
public static void main(String args[]) throws InterruptedException {
startReport();
ObjectRunner runner = new ObjectRunner();
for (int i = 0; i < 5; i++) {
runner.run();
}
Thread.sleep(3000L);
}
static void startReport() {
SharedMetricRegistries.add("objectRunnerRegistryName", registry);
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.outputTo(new PrintStream(System.out))
.build();
reporter.start(3, TimeUnit.SECONDS);
}
}
关键逻辑:
SharedMetricRegistries.add()
将自定义 registry 与注解中指定的名称绑定- 创建
ConsoleReporter
每 3 秒打印一次指标到控制台 - 调用
runner.run()
5 次,触发计时器统计
3.3 输出结果
运行后输出如下:
-- Timers ----------------------------------------------------------------------
ObjectRunner.timerName
count = 5
mean rate = 0.86 calls/second
1-minute rate = 0.80 calls/second
5-minute rate = 0.80 calls/second
15-minute rate = 0.80 calls/second
min = 1000.49 milliseconds
max = 1003.00 milliseconds
mean = 1001.03 milliseconds
stddev = 1.10 milliseconds
median = 1000.54 milliseconds
75% <= 1001.81 milliseconds
95% <= 1003.00 milliseconds
98% <= 1003.00 milliseconds
99% <= 1003.00 milliseconds
99.9% <= 1003.00 milliseconds
输出解读:
- ✅
count=5
:方法被调用了 5 次 - ✅
mean=1001ms
:平均耗时约 1 秒,符合预期 - ✅ 百分位数据(p95、p99)可用于分析延迟分布,排查毛刺
- ✅ 吞吐量(rate)反映单位时间处理能力
💡 踩坑提醒:如果输出为空,请检查是否通过
mvn compile
编译,而非直接 IDE 运行。AspectJ 编译插件必须参与构建过程。
4. 总结
本文演示了如何使用 Metrics + AspectJ 实现零侵入的方法级性能监控:
- ✅ 无需依赖 Spring 等容器,纯 Java 项目也可用
- ✅ 编译期织入,运行时无反射开销,性能友好
- ✅ 一行注解搞定埋点,开发效率高
- ✅ 提供丰富的统计指标(均值、百分位、速率等)
该方案特别适合中间件、工具类、独立服务等场景的性能分析。对于已有项目,也可逐步接入,风险可控。
完整示例代码已托管至 GitHub:https://github.com/tech-tutorial/metrics-aspectj-demo