使用AspectJ LTW允许Spring-proxy功能自我调用非公共方法和相关注意事项

use*_*710 6 spring aspectj spring-aop spring-boot spring-aspects

我已经看到了相关的春天功能的例子不胜枚举@Cacheable,@Transactional,@Async其中相同的选项重申每次等:

  1. 通过代理对象进行的自我调用通过或者ApplicationContext.getBean(MyService.class)自动装配的MyService.class代理对象获得@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS),

  2. 将目标方法重定位到单独的@Service类,

  3. 使用AspectJ加载时编织.

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


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

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

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

这个问题来自spring-boot用户的观点,但我认为它通常适用于Spring AOP和AspectJ LTW.如果在这种情况下需要特殊考虑,请纠正我.

Ind*_*sak 5

  1. 在使用AspectJ LTW的前两个选项实现上述行为方面,是否有一些不错的示例?

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

  1. 使用AspectJ的Spring Boot源代码编织示例
  2. 使用AspectJ的Spring Boot加载时编织示例

除了将方面编织到目标类别中的方式之外,这两个示例都是相似的。

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

  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());
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 通过调用方方法的名称。

    @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());
    }
    
    Run Code Online (Sandbox Code Playgroud)

您可以在此处找到工作示例。

更新

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

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

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

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

问:在您的示例中,我看到您没有以任何特殊方式启动CTW的应用程序。这足够了,还是我错过了任何事情?

是的,在CTW中,启动期间不需要任何特殊操作,因为ajc在编译时,AspectJ编译器()已在原始代码中注入了额外的字节码。例如,这是原始源代码:

@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;
}
Run Code Online (Sandbox Code Playgroud)

这是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;
}
Run Code Online (Sandbox Code Playgroud)

在LTW中,您需要Java代理,因为在加载时(即Java类加载器正在加载类时)会修改代码。