使用 AspectJ LTW 允许 spring 代理功能自调用非公共方法及相关注意事项

2024-05-05

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

  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 and @Transactional只是@Cacheable?一个示例用例可能是:由于团队的设计,所有可缓存方法(其中一些可能是私有的)应位于外观类中,该类调用执行繁重计算的私有方法,但可能会更新某些状态(即'last-queried-at: #') 在返回到外部调用之前。

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


  1. 是否有任何不错的示例来启用通常使用 AspectJ LTW 的前两个选项来实现上述行为?

我的 GitHub 帐户上有几个 AspectJ 示例。这两个示例都展示了如何拦截内部调用同一目标物体(自调用)并且还拦截私有方法.

  1. 使用 AspectJ 的 Spring Boot 源代码编织示例 https://github.com/indrabasak/spring-source-weaving-example
  2. 使用 AspectJ 的 Spring Boot 加载时编织示例 https://github.com/indrabasak/spring-loadtime-weaving-example

除了方面融入目标类的方式之外,这两个示例都很相似。

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

  1. 有没有办法选择性地启用 AspectJ LTW,例如不适用于@Async和@Transactional,而仅适用于@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 https://github.com/indrabasak/spring-loadtime-weaving-example.

Updated

Q.那么我的理解是否正确,如果我想启用编译时编织以实现事务性,我会: 1. 不再在数据源配置中的任何位置使用 TransactionAwareDataSourceProxy; 2. 将以下内容添加到我的应用程序中:@EnableTransactionManagement(mode=AdviceMode.ASPECTJ)。

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

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

你会需要@EnableTransactionManagement如果你想启用Spring的注解驱动的事务管理能力。

Q.在您的示例中,我发现您没有以任何特殊方式启动 CTW 应用程序。这足够了,还是我错过了什么?

是的,在 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$0;
    if (ajc$anno$0 == null) {
        var10002 = ajc$anno$0 = 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 代理,因为代码在加载时(即,当 Java 类加载器加载类时)会被修改。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 AspectJ LTW 允许 spring 代理功能自调用非公共方法及相关注意事项 的相关文章

随机推荐