使用 AspectJ LTW 允许 spring 代理功能对非 public 方法的自调用和相关注意事项

Using AspectJ LTW to allow spring proxy functionalty on self-invocation of non-public methods and related considerations

我见过许多与 @Cacheable@Transactional@Async 等相关的 Spring 功能示例,其中每次都重复相同的选项:

  1. 通过 ApplicationContext.getBean(MyService.class) 或自动装配的 MyService.class 代理对象以及 @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)

    获得的代理对象进行的自调用
  2. 将目标方法重新定位到单独的 @Service class,

  3. 使用 AspectJ 加载时编织。

虽然前两种方法通常很好,但有时我们需要将上述三个(和其他)注释的功能附加到私有方法,无论是出于代码清晰度、设计还是其他原因。


前两种方法的例子很多,但后两种方法的例子很少。据我了解,由于 AspectJ LTW 的性质,默认情况下它与通常的 Spring AOP 行为互斥,后者可以轻松实现 @Cacheable 等行为。我的问题如下:

  1. 是否有任何像样的示例来启用通常使用 AspectJ LTW 的前两个选项完成的上述行为?

  2. 有没有办法选择性地启用 AspectJ LTW,例如不是 @Async@Transactional,而是 @Cacheable?一个示例用例可能是:由于团队的设计,所有可缓存的方法(其中一些可能是私有的)应该位于外观 class 中,它调用执行大量计算的私有方法,但可能会更新在返回外部调用之前的一些状态(即'last-queried-at: #')。

这个问题是从 spring-boot 用户的角度提出的,但我相信它普遍适用于 Spring AOP 和 AspectJ LTW。如果在这种情况下需要特殊考虑,请指正。

  1. Are there any decent examples on enabling the above behavior usually done with the first two options using AspectJ LTW?

我的 GitHub 帐户上有几个 AspectJ 示例。这两个示例都展示了如何拦截 同一目标对象 内的调用(自调用)以及拦截 私有方法 .

  1. Spring Boot Source Weaving Example with AspectJ
  2. Spring Boot Load-Time Weaving Example with AspectJ

这两个示例都相似,只是将方面编织到目标 classes 中的方式不同。

请阅读示例的自述文件以了解有关每种编织类型以及如何使用每个示例的更多信息。

  1. Is there a way to enable AspectJ LTW selectively, e.g. not for @Async and @Transactional but just @Cacheable?

是的,您可以根据以下任一条件进行过滤:

  • 通过调用方法的注解类型。

    @Before("call(* com.basaki.service.UselessService.sayHello(..))" +
            "  && cflow(@annotation(trx))")
    public void inspectMethod(JoinPoint jp,
            JoinPoint.EnclosingStaticPart esjp, Transactional trx) {
        log.info(
                "Entering FilterCallerAnnotationAspect.inspectMethod() in class "
                        + jp.getSignature().getDeclaringTypeName()
                        + " - method: " + jp.getSignature().getName());
    }
    
  • 通过调用方方法的名称。

    @Before("call(* com.basaki.service.UselessService.sayHello(..))" +
            "  && cflow(execution(* com.basaki.service.BookService.read(..)))")
    public void inspectMethod(JoinPoint jp,
            JoinPoint.EnclosingStaticPart esjp) {
        log.info(
                "Entering FilterCallerMethodAspect.inspectMethod() in class "
                        + jp.getSignature().getDeclaringTypeName()
                        + " - method: " + jp.getSignature().getName());
    }
    

您可以找到工作示例 here

已更新

Q. Do I understand correctly then, that if I wanted to enable compile-time weaving for transactionality, I would: 1. No longer use a TransactionAwareDataSourceProxy anywhere in my DataSource configuration; 2. Add the following to my application: @EnableTransactionManagement(mode=AdviceMode.ASPECTJ).

Spring AOP 和 CTW/LTW AspectJ 编织是完全正交的,即它们彼此独立。

  • compileLTW修改了实际的字节码,即将代码行插入到目标对象的方法体中。
  • AOP 是基于代理的,即目标对象周围有一个包装器。对目标对象的任何调用都会被 Spring 包装器对象拦截。如果需要,您还可以将 Spring AOP 与 CTW/LTW 一起使用。在这种情况下,Spring AOP 将对修改后的目标进行代理。

如果您想启用 Spring 的注释驱动事务管理功能,您将需要 @EnableTransactionManagement

Q. In your examples, I see that you do not start the application in any special way for CTW. Would this suffice, or have I missed anything?

是的,在 CTW 中,您在启动期间不需要任何特殊的东西,因为额外的字节码已经在编译期间由 AspectJ 编译器 (ajc) 注入到原始代码中。例如,这里是原始源代码:

@CustomAnnotation(description = "Validates book request.")
private Book validateRequest(BookRequest request) {
    log.info("Validating book request!");

    Assert.notNull(request, "Book request cannot be empty!");
    Assert.notNull(request.getTitle(), "Book title cannot be missing!");
    Assert.notNull(request.getAuthor(), "Book author cannot be missing!");

    Book entity = new Book();
    entity.setTitle(request.getTitle());
    entity.setAuthor(request.getAuthor());

    return entity;
}

这是AspectJ编译器编译后的同一段代码,ajc:

private Book validateRequest(BookRequest request) {
    JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, request);
    CustomAnnotationAspect var10000 = CustomAnnotationAspect.aspectOf();
    Annotation var10002 = ajc$anno[=13=];
    if (ajc$anno[=13=] == null) {
        var10002 = ajc$anno[=13=] = BookService.class.getDeclaredMethod("validateRequest", BookRequest.class).getAnnotation(CustomAnnotation.class);
    }

    var10000.inspectMethod(var3, (CustomAnnotation)var10002);

    log.info("Validating book request!");
    Assert.notNull(request, "Book request cannot be empty!");
    Assert.notNull(request.getTitle(), "Book title cannot be missing!");
    Assert.notNull(request.getAuthor(), "Book author cannot be missing!");

    Book entity = new Book();
    entity.setTitle(request.getTitle());
    entity.setAuthor(request.getAuthor());
    return entity;
}

在 LTW 中,您需要 Java 代理,因为代码在加载期间被修改,即当 classes 被 Java class 装载机。