首页 > 解决方案 > Micrometer TimedAspect 不会拦截对使用 @Timed 注释的方法的调用

问题描述

我正在尝试使用Micrometer在我的 Java 应用程序中记录执行时间。这与我关于使用注释的其他问题有关。@Timed

我有一个CountedObject具有以下两种方法的类:

@Measured
@Timed(value = "timer1")
public void measuredFunction() {
    try {
        int sleepTime = new Random().nextInt(3) + 1;
        Thread.sleep(sleepTime * 1000L);
    } catch (InterruptedException e) {}
}

@Timed(value = "timer2")
public void timedFunction() {
    try {
        int sleepTime = new Random().nextInt(3) + 1;
        Thread.sleep(sleepTime * 1000L);
    } catch (InterruptedException e) {}
}

我已经定义了一个自定义注释@Measured

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Measured {
    
}

MeasuredAspect拦截对使用我的注释进行注释的方法的调用@Measured

@Aspect
public class MeasuredAspect {
    @Around("execution(* *(..)) && @annotation(Measured)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        return AppMetrics.getInstance().handle(pjp);

    }
}

在我的AppMetrics课程中,我初始化了千分尺的TimedAspect实例,并在handle(ProceedingJoinPoint pjp)方法中将其传递ProceedingJoinPoint pjp给 TimedAspect 实例。

public class AppMetrics {
    private static final AppMetrics instance = new AppMetrics();
    
    private MeterRegistry registry;
    private TimedAspect timedAspect;
    
    public static AppMetrics getInstance() {
        return instance;
    }
    
    private AppMetrics() {
        this.registry = new SimpleMeterRegistry();
        this.timedAspect = new TimedAspect(registry);
    }
    
    public Object handle(ProceedingJoinPoint pjp) throws Throwable {
        return timedAspect.timedMethod(pjp);
    }
}

在我的应用程序主应用程序中,我创建了一个对象CountedObject并调用measuredFunction()timedFunction()然后我检查了[由@Measured 和 @Timed 注释的] 使用的registry.getMeters();唯一timer1 ,而应该由[仅由@注释的timer2定时] 不存在。measuredFunction()timedFunction()

我正在使用带有AspectJ 开发工具插件的 eclipse,我的项目是一个具有 AspectJ 功能的 Gradle 项目。我id "io.freefair.aspectj" version "5.1.1"在我的 Gradle 插件中使用插件。这是一个基本的 Java 应用程序,而不是 Spring 应用程序。

需要进行哪些配置或更改哪些代码,以便千分尺TimedAspect可以直接拦截我的方法调用[即timedFunction()应该定时并且应该在注册表中找到timer2 ],而无需我的自定义注释?

标签: javaaopaspectjmicrometer

解决方案


我为您创建了一个示例项目:

https://github.com/kriegaex/SO_AJ_MicrometerTimed_67803726

引用自述文件(抱歉,StackOverflow 不赞成只包含链接的答案):


https://github.com/micrometer-metrics/micrometer/issues/1149和 StackOverflow 上,关于 Micrometer@Timed注释的常见问题解答是,为什么它与 Spring AOP 一起使用,但在使用 Micrometer 作为本机 AspectJ 的方面库时却不行编译时编织 (CTW) 的上下文,例如使用 AspectJ Maven 插件。aop.xml当提供指向时,它可以与加载时编织 (LTW)一起使用TimedAspect,但在 CTW 中,方面永远不会起作用。

原因是方面是用 Javac 编译的,而不是用 AspectJ 编译器 (AJC) 编译的,这是“完成”Java 类所必需的,即增强其字节码以成为完整的 AspectJ 方面。LTW 代理在类加载期间动态执行此操作,但在 CTW 上下文中,您需要明确告诉 AJC 在 Micrometer 库上进行编译后编织(也称为二进制编织),生成新编织的类文件。这是通过将 Micrometer 放在 AJC 的输入路径上来完成的,以确保其类文件正在被转换并写入目标目录。AspectJ Maven 中的 inpath 是通过<weaveDependencies>. 至少有两种方法可以做到这一点:

  • 您可以在单独的 Maven 模块中创建自己的库的编织版本,然后使用该模块而不是 Micrometer。在这种情况下,您需要在消费模块中排除原始的 Micrometer 库,以确保未编织的类文件不再位于类路径中并被意外使用。

  • 此示例项目中显示的方式是单模块方法,使用 Maven Shade 构建可执行的 uber JAR。Micrometer 类文件不像第一种方法那样是可重用的库,但它非常适合演示目的,因为我们可以只运行示例应用程序并检查其输出:

$ mvn clean package

...
[INFO] --- aspectj-maven-plugin:1.12.6:compile (default) @ SO_AJ_MicrometerTimed_67803726 ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[INFO] Join point 'method-execution(void de.scrum_master.app.Application.doSomething())' in Type 'de.scrum_master.app.Application' (Application.java:23) advised by around advice from 'io.micrometer.core.aop.TimedAspect' (micrometer-core-1.7.0.jar!TimedAspect.class(from TimedAspect.java))
...
[INFO] --- maven-shade-plugin:3.2.4:shade (default) @ SO_AJ_MicrometerTimed_67803726 ---
[INFO] Including org.hdrhistogram:HdrHistogram:jar:2.1.12 in the shaded jar.
[INFO] Including org.latencyutils:LatencyUtils:jar:2.0.3 in the shaded jar.
[INFO] Including org.aspectj:aspectjrt:jar:1.9.6 in the shaded jar.
[INFO] Excluding io.micrometer:micrometer-core:jar:1.7.0 from the shaded jar.
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing C:\Users\me\java-src\SO_AJ_MicrometerTimed_67803726\target\SO_AJ_MicrometerTimed_67803726-1.0-SNAPSHOT.jar with C:\Users\me\java-src\SO_AJ_MicrometerTimed_67803726\target\SO_AJ_MicrometerTimed_67803726-1.0-SNAPSHOT-shaded.jar
[INFO] Dependency-reduced POM written at: C:\Users\me\java-src\SO_AJ_MicrometerTimed_67803726\target\dependency-reduced-pom.xml
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

$ java -jar target/SO_AJ_MicrometerTimed_67803726-1.0-SNAPSHOT.jar

Juni 05, 2021 1:12:27 PM io.micrometer.core.instrument.push.PushMeterRegistry start
INFO: publishing metrics for LoggingMeterRegistry every 1m
Juni 05, 2021 1:13:00 PM io.micrometer.core.instrument.logging.LoggingMeterRegistry lambda$publish$5
INFO: method.timed{class=de.scrum_master.app.Application,exception=none,method=doSomething} throughput=0.166667/s mean=0.11842469s max=0.2146482s

请特别注意这些日志行(插入换行符以提高可读性):

Join point 'method-execution(void de.scrum_master.app.Application.doSomething())'
  in Type 'de.scrum_master.app.Application' (Application.java:23)
  advised by around advice from 'io.micrometer.core.aop.TimedAspect'
  (micrometer-core-1.7.0.jar!TimedAspect.class(from TimedAspect.java))

以上证明@Timed注释实际上导致 MicrometerTimedAspect被编织到我们的应用程序代码中。以下是方面为示例应用程序创建的测量值:

method.timed
  {class=de.scrum_master.app.Application,exception=none,method=doSomething}
  throughput=0.166667/s mean=0.11842469s max=0.2146482s

推荐阅读