AspectJ designator @args() not working in Spring A

2019-07-12 22:50发布

问题:

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

回答1:

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.