Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 762
filpa Avatar asked Jan 28 '18 22:01

filpa


1 Answers

  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.

like image 164
Indra Basak Avatar answered Oct 04 '22 13:10

Indra Basak