2

I am learning Spring and I searched a lot about how to properly use @args() AspectJ designator but I am still not clear completely. What I know about it is that it limits joint-point matches to the execution of methods whose arguments are annoted with the given annotation types. This does not seem to work in my case.

So here goes my files:

Human.java

@Component
public class Human {
    int sleepHours;
    public int sleep(String sleepHours) {
        this.sleepHours = Integer.parseInt(sleepHours);
        System.out.println("Humans sleep for " + this.sleepHours + " hours.");
        return this.sleepHours+1;
    }
}

Sleepable.java - Sleepable annotation

 package com.aspect;
 public @interface Sleepable {

 }

SleepingAspect.java - Aspect

@Component
@Aspect
public class SleepingAspect {

    @Pointcut("@args(com.aspect.Sleepable)")
    public void sleep(){};

    @Before("sleep()")
    public void beforeSleep() {
        System.out.println("Closing eyes before sleeping");
    }

    @AfterReturning("sleep()")
    public void afterSleep() {
        System.out.println("Opening eyes after sleep");
    }
}

MainApp.java

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Human human = (Human) context.getBean("human");
        @Sleepable
        String sleepHours = "8";
        human.sleep(sleepHours);
    }
}

Output

Humans sleep for 8 hours.

Expected Output

Closing eyes before sleeping

Humans sleep for 8 hours.

Opening eyes after sleep

ash
  • 156
  • 3
  • 12

1 Answers1

2

You have several mistakes in your code:

  • Spring AOP can only intercept method calls upon Spring components. What you are trying to intercept is an annotation on a local variable. Not even the much more powerful AspectJ can intercept anything concerning local variables, only read/write access to class members. Thus, what you are trying to do is impossible. And by the way, it is bad application design. Why would anyone want to rely on method internals when trying to apply cross-cutting behaviour? Method internals are subject to frequent refactoring. Suggestion: Put your annotation on method public int sleep(String sleepHours).
  • Your annotation is invisible during runtime because your forgot to add a meta annotation like @Retention(RetentionPolicy.RUNTIME).
  • @args is the wrong pointcut type. It captures method arguments the types of which are annotated. You want to use @annotation(com.aspect.Sleepable) instead.

I think you should not try a copy & paste cold start with Spring AOP but read the Spring AOP manual first. Everything I explained here can be found there.


Update: So according to you comments you were just practicing and trying to make up an example for @args(). Here is one in plain AspectJ. You can easily use it in similar form in Spring AOP. The @Before advice shows you how to match on an argument with an annotation on its class, the @After advice also shows how to bind the corresponding annotation to an advice argument.

Annotation + class using it:

package com.company.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}
package com.company.app;

@MyAnnotation
public class MyClass {}

Driver application:

package com.company.app;

public class Application {
  public static void main(String[] args) {
    new Application().doSomething(new MyClass(), 11);
  }

  public String doSomething(MyClass myClass, int i) {
    return "blah";
  }
}

As you can see, here we use the annotated class MyClass in a method argument. This can be matched with @args() in the following aspect.

Aspect:

package com.company.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import com.company.app.MyAnnotation;

@Aspect
public class MyAspect {
  @Before("@args(com.company.app.MyAnnotation, ..)")
  public void myBeforeAdvice(JoinPoint thisJoinPoint) {
    System.out.println("Before " + thisJoinPoint);
  }

  @After("@args(myAnnotation, ..)")
  public void myAfterAdvice(JoinPoint thisJoinPoint, MyAnnotation myAnnotation) {
    System.out.println("After " + thisJoinPoint + " -> " + myAnnotation);
  }
}

Console log:

Before call(String com.company.app.Application.doSomething(MyClass, int))
Before execution(String com.company.app.Application.doSomething(MyClass, int))
After execution(String com.company.app.Application.doSomething(MyClass, int)) -> @com.company.app.MyAnnotation()
After call(String com.company.app.Application.doSomething(MyClass, int)) -> @com.company.app.MyAnnotation()

Of course, call() joinpoints are unavailable in Spring AOP, so there you would only see two lines of log output.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks @kriegaex. Actually, it may be a bad application design. I am just trying to learn by experimenting. I know the use of `@annotation`, what I am trying to do is to understand the use of `@args`. And what I did here is according to the manual itself. The manual says "`@args` - limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)". The manual does not give any example of `@args`. So I tried doing it according to the explanation given. – ash Apr 03 '17 at 07:03
  • Maybe these are the points I need to consider: 1. Spring AOP can only intercept method calls upon Spring components, 2. Your annotation is invisible during runtime because your forgot to add a meta annotation like @Retention(RetentionPolicy.RUNTIME). Will try implementing these points. Thanks. – ash Apr 03 '17 at 07:13
  • The manual, as you are quoting it, says it correctly. I added an `@args()` example to my answer for your convenience and better understanding. – kriegaex Apr 03 '17 at 09:29
  • Your example helped a lot. – ash Apr 04 '17 at 05:06
  • I tried your example, but it fails with `No visible constructors in class org.springframework.hateoas.config.HypermediaSupportBeanDefinitionRegistrar$DefaultObjectMapperCustomizer` – Tiina Jan 02 '18 at 02:45
  • No, you did not. You must have modified something. Please do not hijack other people's questions but create a new question with an [MCVE](http://stackoverflow.com/help/mcve) and mention in the question that it is related to my answer here. Feel free to notify me of the new question here in another comment, then I can look into it. – kriegaex Jan 02 '18 at 07:08
  • @kriegaex I have posted a question several hours ago. And I have found that `@Component` with `@AspectJ` is the problem, if aspect bean is registered using schema, then that odd error is gone. No idea why. https://stackoverflow.com/questions/48055097/spring-aop-no-visible-constructors-in-class – Tiina Jan 02 '18 at 07:33
  • Well, how could I know that you have already posted another question before if you do not add a link to it right away? Okay, I will look into it. – kriegaex Jan 02 '18 at 10:33