9

I have seen numerous examples of Spring functionality related to @Cacheable, @Transactional, @Async, etc. where the same options are reiterated every time:

  1. Self invocation made through a proxy object gotten through either the ApplicationContext.getBean(MyService.class) or an autowired MyService.class proxy object in addition to @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS),

  2. Relocating the target method to a separate @Service class,

  3. Using AspectJ load-time weaving.

While the first two approaches are usually fine, there are times when we need to attach the functionality of the above three (and other) annotations to private methods, whether it be for code clarity, design, or other reasons.


There are many example of the first two approaches, but very few of the last. As I understand, due to the nature of AspectJ LTW, by default it is mutually exclusive with the usual Spring AOP behaviour that enables the @Cacheable, etc. behaviour without much hassle. My questions are as follows:

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

  2. Is there a way to enable AspectJ LTW selectively, e.g. not for @Async and @Transactional but just @Cacheable? An example use case of this would perhaps be: due to a team's design, all cacheable methods (some of which may be private) should be located in a facade class which calls private methods that perform heavy calculations, but might update some state (i.e. 'last-queried-at: #') before returning to the external called.

This question is from the viewpoint of a spring-boot user, but I believe it applies generally to Spring AOP and AspectJ LTW. Please correct me if there are special considerations needed in this case.

filpa
  • 3,651
  • 8
  • 52
  • 91
  • 3
    AspectJ is superior in pretty much all aspects to the proxy based solution of Spring AOP, so there is no point in trying to have both at the same time. Once you enabled AspectJ LTW for your app, you should disable the proxy based stuff by removing `@EnableAspectJAutoProxy` and configuring your aspects via `@Bean` factory methods in case you have aspects that need to be configured by spring. – Nándor Előd Fekete Jan 28 '18 at 23:44
  • What kind of effort would be required to switch existing `@Cacheable`, `@Transactional`, `@Async`, as well as the proxy functionality of Spring AOP to AspectJ? Are there any available examples that showcase this use case (even better if there is a consideration of pros vs. cons)? – filpa Jan 30 '18 at 09:40
  • Not much effort. Try doing what I said in my earlier comment. Not sure what you mean by the proxy functionality of Spring AOP, but AspectJ does more than that in a different, but better way, so if you switch out Spring AOP with AspectJ that's gonna be better. The only thing that would probably require extra work is supporting the old way of defining pointcuts and advices in xml configuration for Spring AOP. `@Cacheable`, `@Transactional` and `@Async` are all supported by aspects in the _spring-aspects_ module. – Nándor Előd Fekete Feb 05 '18 at 00:14
  • 1
    Please think a little about someone who will be maintaining that code after you. Will they thank you for all additional complexity introduced by aspects? Will it be easier (or at all possible) to debug that code? – Devstr Feb 09 '18 at 23:39

1 Answers1

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

I have a couple of AspectJ examples on my GitHub account. Both these examples show how to intercept calls within the same target object (self-invocation) and also intercept private methods.

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

Both the examples are similar except the way aspects are woven into target classes.

Please read the examples' READMEs to find out more about each type of weaving and on how to use each of the examples.

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

Yes, you can filter based on either of the following:

  • By the annotation type of the caller method.

    @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());
    }
    
  • By the name of the caller method.

    @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());
    }
    

You can find working examples here.

Updated

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 and CTW/LTW AspectJ weavings are completely orthogonal, i.e., they are independent of each other.

  • The compile and LTW modify the actual bytecode, i.e, the lines of code are inserted in the target object's method body.
  • AOP is proxy-based, i.e. there is a wrapper around the target object. Any call to the target object gets intercepted by the Spring wrapper object. You can also use Spring AOP with CTW/LTW if the need arises. In this case, Spring AOP will make a proxy of the modified target.

You will need @EnableTransactionManagement if you want to enable Spring's annotation-driven transaction management capability.

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?

Yes, in CTW you don't need anything special during start-up since the extra bytecode is already injected in the original code by the AspectJ compiler (ajc) during compile time. For example, here is the original source code:

@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;
}

Here is the same piece of code after compilation by AspectJ compiler, 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;
}

While in LTW, you need the Java Agent since the code gets modified during load-time, i.e., when the classes are being loaded by Java class loaders.

Indra Basak
  • 7,124
  • 1
  • 26
  • 45
  • Great stuff, thank you very much for your contributions! 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)`. 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? – filpa Feb 13 '18 at 11:50
  • Updated my entry with answers to your questions. – Indra Basak Feb 14 '18 at 19:32
  • Thank you very much for your in-depth answer. I believe this clarifies everything. – filpa Feb 15 '18 at 10:59
  • It would be great if you add performance of all three approaches too, I mean CTW,LTW and proxy – Anil Gowda Aug 11 '20 at 08:56