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

时间:2018-01-28 22:43:44

标签: spring spring-boot aspectj spring-aop spring-aspects

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

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

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

  3. 使用AspectJ加载时编织。

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

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

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

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

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

1 个答案:

答案 0 :(得分:3)

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

我的GitHub帐户上有几个AspectJ示例。这两个示例都展示了如何拦截相同目标对象(自我调用)中的调用,并拦截私有方法

  1. Spring Boot Source Weaving Example with AspectJ
  2. Spring Boot Load-Time Weaving Example with AspectJ
  3. 除了将方面编织到目标类中之外,两个示例都是相似的。

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

      
        
    1. 有没有办法有选择地启用AspectJ LTW,例如不是@Async和@Transactional而是@Cacheable?
    2.   

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

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

      @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

    更新

      

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

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

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

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

      

    问。在您的示例中,我看到您没有以任何特殊方式为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类加载器加载类时。