【问题标题】:Micrometer TimedAspect doesn't intercept calls to methods annotated with @TimedMicrometer TimedAspect 不会拦截对使用 @Timed 注释的方法的调用
【发布时间】:2021-06-02 10:51:38
【问题描述】:

我正在尝试使用Micrometer 在我的 Java 应用程序中记录执行时间。这与我的其他question 关于使用的@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() 然后我检查了我的 registry.getMeters();timer1 使用 measuredFunction() [这是找到了@Measured 和@Timed 都注释的],而timedFunction() [仅由@Timed 注释] 应该使用的timer2 不存在。

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

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

【问题讨论】:

    标签: java aop aspectj micrometer


    【解决方案1】:

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

    https://github.com/kriegaex/SO_AJ_MicrometerTimed_67803726

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


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

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

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

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

    $ 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 注释实际上导致Micrometer 的TimedAspect 被编织到我们的应用程序代码中。这里是 方面为示例应用程序创建的测量值:

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

    【讨论】:

    • gradle 中有没有类似<weaveDependencies> 的东西?因为我们需要在项目构建中使用 gradle 而不是 maven。我们是否需要一个特殊的 gradew build 命令用于 aspectJ?这是我的test project 的 repo,如果你能检查一下,将不胜感激。
    • 我不是 Gradle 用户,但和你一样,我查看了 Freefair 的文档。我猜,你想使用io.freefair.aspectj.post-compile-weaving。或者,就像 Jonatan 说的那样,您可以简单地使用 LTW(如果编译时编织不是强制性的),即使用 java -javaagent:/path/to/aspectjweaver.jar 和对应的 aop.xml 文件启动您的 JVM,参考 TimedAspect。在这种情况下,您完全可以不使用 AspectJ 编译器。
    • 在我的应用范围内,我不负责启动 JVM。我构建我的项目并将其作为 jar 提供给使用它的应用程序。所以我想编译后编织应该解决这个问题吗?我将研究如何使用Gradlefreefair 做到这一点。对于post-compile-weaving 或其他库,您有其他选择吗?
    • 就像我说的,我不使用 Gradle。我很少构建一些示例项目来帮助这里的人。在 Gradle 上,似乎没有像 AspectJ Maven Plugin 那样用于 Maven 项目的事实上的标准,但是为什么您要玩弄很多球而不是专注于您现在正在使用的那个并首先学习如何正确设置它呢?您必须模仿我在自己的解决方案或我在上面描述的替代方案中所做的事情。不会有神奇的解决方案为您完成全部工作。只需挖掘并解决它。如果有问题,请提出一个新的、更具体的问题。
    • 我不是在寻找替代品。您的解决方案有效,但不适用于 gradle,这在原始问题中有所说明。我正在努力让它工作,所以我可以将你的问题标记为正确并添加 gradle 部分。谢谢
    【解决方案2】:

    我不确定您对此有何期望:

    public Object handle(ProceedingJoinPoint pjp) throws Throwable {
        return timedAspect.timedMethod(pjp);
    }
    

    如果我理解正确,它什么也不做。 您可以按照guides 为您的项目正确设置 AspectJ。完成后TimedAspect 应该可以工作,你不需要MeasuredAspect@Measured 只需设置AspectJ。

    【讨论】:

    • 在我的 AppMetrics 构造函数中,我初始化了千分尺的 TimedAspect this.timedAspect = new TimedAspect(registry); 的一个实例,这是千分尺的一个方面,它拦截带有 @Timed 注释的方法。那么handle 是什么让 ProceedingJoinPoint 通过千分尺TimedAspect 处理,就像@Timed 工作一样。
    • 我看到了,但又一次:如果没有正确设置 AspectJ,您的代码将无法执行任何操作。使用 AspectJ 设置,您不需要 MeasuredAspect 或 @Measured。 :)
    • 具体来说,这里我们确实需要编译后编织,因为Micrometer中的aspect类文件是一个简单的Java类,即我们需要确保AspectJ编译后将aspect转换为真正的AspectJ aspect第一的。因此,在这种情况下,“正确设置 AspectJ”绝非易事。因此,您的回答并没有真正的帮助。
    • 这个问题不知何故缺失了。我认为我的回答应该回答这个问题。 TimedAspect 适用于运行时编织。我认为从这个角度来看 MeasuredAspect 是一样的。
    • 在谈到 AspectJ 时,您可能指的是加载时编织,而运行时编织让我想起了 Spring AOP。我没有看到问题中缺少任何内容,OP 提到了 Gradle AspectJ 编译器插件(Freefair)。所以很明显他想用CTW。
    猜你喜欢
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-07
    • 2011-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多