当对构造函数使用@Around 建议时,AspectJ weaver 给了我“不兼容的返回类型应用于构造函数执行”

问题描述

我正在尝试使用@Around 建议将用于跟踪的上下文注入到新创建的 Callable 对象中:

@Aspect
@Configurable
public class TracingAspect {

    @Around("execution(java.util.concurrent.Callable+.new(..))")
    public Callable wrapExecutor(ProceedingJoinPoint pjp) throws Throwable {
        Context context = Context.current();
        return context.wrap((Callable) pjp.proceed());
    }
}

当编织者遇到一个预期的连接点时,例如下面例子中的匿名 Callable 实现:

public class Foo {

    private ExecutorService threadpool = Executors.newFixedThreadPool(10);

    public Future<String> doStuffAsync() throws InterruptedException {

        return threadpool.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                System.out.println(42);
                return 42;
            }
        });
    }
}

我从 Aspectj weaver 收到以下错误消息:

error at foo/bar/tracing/aspect/TracingAspect.java::0 incompatible return type applying to constructor-execution(void foo.bar.Foo$1.<init>(foo.bar.Foo))

在这里做错了什么?

解决方法

首先,你不能在构造函数 execution() 中替换返回的对象,因为构造函数不是一个普通的方法,它可以例如有一个超类型或接口作为返回类型。构造函数总是准确地返回定义类型的对象,没有别的。这甚至不是 AspectJ 的限制,而是 JVM 的限制。使用 ASM 之类的字节码工程库,您会遇到同样的限制。

所以在 AspectJ 中你能做的最好的事情是在构造函数 call() 中替换返回的对象,但该对象也必须匹配预期的类型。不幸的是,OpenTelemetry 返回一个 lambda 实例,该实例无法转换为您代码中的确切匿名 Callable 子类型。这意味着,这种代码结构在这里你无能为力。

作为一种解决方法,您可以拦截对采用 Callable 实例(例如 ExecutorService.submit(Callable))的方法的调用。您只需要确保捕获所有相关的。例如:

package de.scrum_master.app;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;

public class Application {
  private ExecutorService threadpool = Executors.newFixedThreadPool(10);

  public Future<Integer> doStuffAsync() throws InterruptedException {
    return threadpool.submit(new Callable<Integer>() {
      @Override
      public Integer call() throws Exception {
        return 42;
      }
    });
  }

  public Future<Integer> doStuffLambdaAsync() throws InterruptedException {
    return threadpool.submit(() -> 77);
  }

  public static void main(String[] args) throws Exception {
    Application app = new Application();
    System.out.println("Future value = " + app.doStuffAsync().get());
    System.out.println("Future value (lambda) = " + app.doStuffLambdaAsync().get());
  }
}
package de.scrum_master.aspect;

import java.util.concurrent.Callable;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import io.opentelemetry.context.Context;

@Aspect
public class TracingAspect {
  @Around("call(* java.util.concurrent.ExecutorService.submit(*)) && args(callable)")
  public Object wrapExecutor(ProceedingJoinPoint pjp,Callable<?> callable) throws Throwable {
    System.out.println(pjp);
    Context context = Context.current();
    return pjp.proceed(new Object[] { context.wrap(callable) });
  }
}
call(Future java.util.concurrent.ExecutorService.submit(Callable))
Future value = 42
call(Future java.util.concurrent.ExecutorService.submit(Callable))
Future value (lambda) = 77

当然,如果您能够准确地确定这些调用,您也可以过滤具有某些属性的可调用调用的拦截调用,例如从哪些类在哪些包中进行调用等。