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);
    }
}

关键逻辑:

  1. SharedMetricRegistries.add() 将自定义 registry 与注解中指定的名称绑定
  2. 创建 ConsoleReporter 每 3 秒打印一次指标到控制台
  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


原始标题:@Timed Annotation Using Metrics and AspectJ