Overview of Spring annotation driven AOP

In this post I will show you how to use Spring aspects. In contrast to AspectJ aspects that are implemented by either compile time or load time bytecode manipulation (process often called waving), Spring aspects are implemented using proxy classes. The main advantage of proxy aspect implementation is ease of use, the main disadvantage is reduced functionality of aspects (e.g. we cannot intercept calls to static methods).

Diagram below illustrates how proxy classes are used to add functionality contained in Aspect I and Aspect II to MyComponentImpl class: Proxy based aspects in Spring When application asks Spring for MyComponent implementation Spring detects that there are aspects attached to MyComponentImpl class, so instead of returning MyComponentImpl instance Spring creates and returns a proxy. Proxy returned to client implements MyComponent interface and by default redirects all method calls to MyComponentImpl instance. In some situations like e.g. calling a doStuff() method proxy may execute code (called advice) from Aspect I and/or Aspect II, thus adding functionality to MyComponentImpl class.

Application setup

Before we create our first aspect we need to setup our application. Let’s start with Maven dependencies. We will need spring-aspects and aspectjweaver libraries. You may be wondering why we need something from AspectJ which we don’t use. Spring Framework supports not only proxy based aspects but also full blown AspectJ aspects, to avoid code duplication designers of Spring decided to borrow annotation definitions like @Aspect from AspectJ project. Thus aspectjweaver is only used as an annotation library and nothing more.

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>4.3.3.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.3.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>3.2.0.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.9</version>
</dependency>

Next we need to create Spring configuration class and bootstrap Spring context:

package io.mc.springaspects;
import org.springframework.context.annotation.ComponentScan;

@Configuration
@ComponentScan("io.mc.springaspects")
@EnableAspectJAutoProxy
public class SpringConfiguration { }

NOTE: To enable proxy based aspects we need to add @EnableAspectJAutoProxy annotation to our configuration class.

public static void main(String... args) throws Exception {
   AnnotationConfigApplicationContext appContext = 
      new AnnotationConfigApplicationContext(
         SpringConfiguration.class);
  // context ready to use!!!
  appContext.close();
} 

Creating aspects

Before we start let’s introduce some terminology. Advice is a piece of code that should be executed when certain event like calling a method takes place. A pointcut is a set of events, for example all calls to a method foo() contained in class Bar. A join point is a single event for example a particular invocation of method foo() on line 105 in class Bar. Aspect is a pointcut-advice pair, in other words aspect defines what should be done (advice) and when (pointcut). Spring borrowed this terminology from AspectJ, it takes a while to get used to it.

We will start by crating @Before and @After aspects that as their names suggest are invoked before and after a method call. Our aspects will add functionality to the following component:

package io.mc.springaspects;

@Component
public class DummyComponent {
    private final ConsoleLogger consoleLogger;
    
    @Autowired
    public DummyComponent(ConsoleLogger consoleLogger) {
        this.consoleLogger = consoleLogger;
    }

    public void doStuff() {
        consoleLogger.log("DummyComponent::doStuff");
    }
}

Let’s start with @Before aspect (here I use the same name for aspect and advice, but remember that they are two different things):

@Aspect
@Component
@Order(100)
public class DummyAspect {
    private final ConsoleLogger consoleLogger;
    
    @Autowired
    public DummyAspect(ConsoleLogger consoleLogger) {
        this.consoleLogger = consoleLogger;
    }
    
    @Pointcut("execution(* io.mc.springaspects.DummyComponent.doStuff())")
    protected void doStuff() { }
    
    @Before("doStuff()")
    public void beforeDoStuff(JoinPoint joinPoint) {
      // ...
    }
}

A few things to notice:

  • DummyAspect class will contain definitions of our aspects, we may define many aspects inside a single class
  • DummyAspect must be registered as a Spring component (here I used @Component annotation). This also means that dependency injection works inside aspects
  • Class must be marked with @Aspect annotation otherwise Spring will ignore our aspects
  • Optionally we may provide @Order annotation that specifies order of aspects (e.g. when there is more than one aspect attached to a given method call). The higher the order value the more close the aspect is to the original method call, see the diagram below:

Aspects order

Now let’s see how @Before aspects works. A pointcut defines a set of events (like calling a method) to which we want to attach advices. For example to create a pointcut that will “point” to every invocation of DummyComponent::doStuff method, we may write:

@Pointcut("execution(* io.mc.springaspects.DummyComponent.doStuff())")
protected void doStuff() { }

This code creates named pointcut with doStuff name. Strange expressions passed to @Pointcut annotation is borrowed from AspectJ, it tells Spring that we want to select method calls execution, of methods that may return any type *, and are members of io.mc.springaspects.DummyComponent class, and are named doStuff, and doesn’t take any parameters ().

Now let’s move to the advice:

@Before("doStuff()")
public void beforeDoStuff(JoinPoint joinPoint) {
     consoleLogger.log("BEFORE CALL TO doStuff()");
     
     consoleLogger.log("ARGUMENTS:");
     Arrays.stream(joinPoint.getArgs())
         .map(Object::toString)
         .forEach(arg -> consoleLogger.log("\t%s", arg));
     
     consoleLogger.log("");
}

Advice is implemented as an instance method, first we declare that this is a @Before advice - so it will be called before target method. We also define where we want to use advice by passing pointcut name to @Before annotation. Advice method may optionally have a JoinPoint argument that represents particular invocation of doStuff() method. JoinPoint contains many useful informations like doStuff() method arguments, or value of this reference.

Let’s check if our aspect works:

DummyComponent dummyComponent = 
  appContext.getBean(DummyComponent.class);
dummyComponent.doStuff();
// Output:
// BEFORE CALL TO doStuff()
// ARGUMENTS:
// 
// TestComponent::doStuff

Yay! It is but since doStuff doesn’t have any parameters we can’t test arguments logging. We will fix that now, by adding a single parameter of type String to doStuff method:

// DummyComponent:
public void doStuff(String x) {
   consoleLogger.log("TestComponent::doStuff(x), x = %s", x);
}

Since we changed method signature we must also change our pointcut definition:

@Pointcut("execution(* io.mc.springaspects.DummyComponent.doStuff(..))")
protected void doStuff() { }

We are using .. to tell Spring that method may have any number of arguments. We may also specify exact number and types of arguments for example:

@Pointcut("execution(* io.mc.springaspects.DummyComponent.doStuff(String))")
protected void doStuff() { }

With changed pointcut we now get:

DummyComponent dummyComponent = 
  appContext.getBean(DummyComponent.class);
dummyComponent.doStuff("foo");
// Output
// BEFORE CALL TO doStuff()
// ARGUMENTS:
//  foo
//
// TestComponent::doStuff(x), x = foo
Extracting arguments

Sometimes we want to extract method argument value, instead of manually extracting argument from JoinPoint we may ask Spring to do this for us. Let’s add another parameter to doStuff:

public void doStuff(String x, String y) {
  consoleLogger.log("TestComponent::doStuff(x, y), " +
    "x = %s, y = %s", x, y);
}

Then let’s define another aspect:

@Pointcut("execution(void io.mc.springaspects.DummyComponent.doStuff(String,String))" +
  "&& args(arg1,arg2)")
protected void doStuff2(String arg1, String arg2) { }
    
@Before("doStuff2(arg1, arg2)")
public void beforeDoStuff2(String arg1, String arg2) {
  consoleLogger.log("arg1 = %s, arg2 = %s", arg1, arg2);
}

Here we use args expression to bound method arguments to arg1 and arg2 variables. Then we may use arg1 and arg2 as a parameters in aspect method. Don’t forget to add parameters to pointcut name in @Before annotation otherwise you will get nasty exception with strange and unhelpful message.

Modifying method parameters

Aspects allow us to easily change values of method arguments. For example given method:

public void greetUser(String userName) {
  consoleLogger.log("Hello, %s", userName);
}

We may create aspect toUpperCase that will uppercase user name before passing argument to target method. Unfortunately @Before aspect is not powerful enough to do this, we will need @Around aspect:

@Pointcut("execution(* io.mc.springaspects.DummyComponent.greetUser(String))")
protected void greetUser() { }

@Around("greetUser()")
public void toUpperCase(ProceedingJoinPoint joinPoint) throws Throwable {
  Object[] originalArguments = joinPoint.getArgs();
  
  Object[] newArguments = new Object[1];
  newArguments[0] = ((String)originalArguments[0]).toUpperCase();
  
  joinPoint.proceed(newArguments);
}

@Around advice has a single parameter of type ProceedingJoinPoint that represents pending target method invocation. This is very powerful feature since it allows us to change method parameters and return value, wrap thrown exceptions or even skip method call altogether. Here we simply invoke target method with changed arguments using ProceedingJoinPoint::proceed call.

Modifying return value

Aspects allow us to change return value of a method. For example let’s add method to our DummyComponent class:

public Collection<Integer> getNumbers(int  size) {
   if (size == 0)
      return null;

   return IntStream.range(0, size)
      .mapToObj(Integer::valueOf)
      .collect(toList());
}

Notice that for size equal zero getNumbers returns null. Returning null instead of empty collection isn’t good programming practice let’s change that behaviour using aspects.

We will start with already familiar @Around advice:

@Pointcut("execution(* io.mc.springaspects.DummyComponent.getNumbers(int))")
protected void getNumbers() { }

@Around("getNumbers()")
public Object aroundGetNumbers(ProceedingJoinPoint joinPoint) throws Throwable {
  Object result = joinPoint.proceed();
  
  return (result == null)
    ? Collections.emptyList()
    : result;
}

If @Around advice method returns value then that value will be used as return value of the target method, this is exactly what we do here.

If we only want to access return value we may also use @AfterReturning advice:

@Pointcut("execution(* io.mc.springaspects.DummyComponent.getNumbers(int))")
protected void getNumbers() { }

@AfterReturning(pointcut="getNumbers()", returning="result")
public void add111(Collection<Integer> result) {
  if (result != null)
    result.add(111);
}

Here we cannot change returned value itself but we may call methods on it, set properties etc.

Intercepting exceptions

We may use @Around advice to intercept, handle or wrap exceptions thrown by target method (just add ordinary try..catch statement around proceed call). Here we will concentrate on @AfterThrowing advice that allows us to inspect exceptions thrown by target method.

In DummyComponent we must add method:

public void doThrow() {
  throw new TestException(
    "TestException from TestComponent::doThrow");
}

We must also declare our exception class:

package io.mc.springaspects;

public class TestException extends RuntimeException {
    private static final long serialVersionUID = 
        -7335805942545920111L;

    public TestException(String message) {
        super(message);
    }
}

And pointcut-advice pair:

@Pointcut("execution(* io.mc.springaspects.DummyComponent.doThrow())")
protected void doThrow() { }

@AfterThrowing(pointcut="doThrow()", throwing="ex")
public void doThrowThrows(TestException ex) {
   consoleLogger.log("doThrow() THROWS EXCEPTION: %s",
        ex.getClass().getSimpleName());
}
Aspects and interface methods

So far we only attached advices to concrete classes like DummyComponent, in real applications we often want to attach advices to all implementations of given interface. Fortunately for us Spring supports this scenario. We will start by declaring DummyInterface interface and its two implementations:

package io.mc.springaspects;

public interface DummyInterface {
    void dummyMethod();
}
@Component("implA")
public class DummyInterfaceImplA implements DummyInterface {
    private final ConsoleLogger logger;
    @Autowired
    public DummyInterfaceImplA(ConsoleLogger logger) {
        this.logger = logger;
    }
    @Override
    public void dummyMethod() {
        logger.log("DummyInterface Implementation A");
    }
}
@Component("implB")
public class DummyInterfaceImplB implements DummyInterface {
    ...
}

Now we may attach our advice to DummyInterface::dummyMethod:

@Pointcut("execution(void io.mc.springaspects.DummyInterface+.dummyMethod())")
protected void dummyMethod() { }

@Before("dummyMethod()")
public void beforeDummyMethod(JoinPoint joinPoint) {
  String implementation =
          joinPoint.getTarget().getClass().getSimpleName();
  
  logger.log("Interface method called from class: %s", implementation);
}

In pointcut expression I used DummyInterface+ to select all classes that implement DummyInterface. In pointcut expression we are not limited to a single method, we may use DummyInterface+.*(..) expression to attach advice to all interface methods.

Let’s check if our advice works:

DummyInterface dummyInterface = 
    (DummyInterface) appContext.getBean("implB");
dummyInterface.dummyMethod();
// Output:
// Interface method called from class: DummyInterfaceImplB
// DummyInterface Implementation B
Aspects and annotations

Another common scenario in real life apps is to attach advices to methods marked with given annotation. Let’s start by creating custom annotation:

@Target(METHOD)
@Retention(RUNTIME)
public @interface UseAdviceOnThisMethod {
    String value() default "";
}

Then we must create test method in DummyComponent and mark it with @UseAdviceOnThisMethod annotation:

@UseAdviceOnThisMethod
public void someMethod() {
   consoleLogger.log("Hello, world!");
}

And finally we must create pointcut-advice pair:

@Pointcut("execution(@io.mc.springaspects.UseAdviceOnThisMethod * *.*(..))")
protected void useAdviceOnThisMethodAnnotation() { }    

@Before("useAdviceOnThisMethodAnnotation()")
public void useAdviceBefore(JoinPoint joinPoint) {
  consoleLogger.log("[ASPECT] SOURCE LOCATION: %s",
      joinPoint.getSourceLocation());
}

In pointcut expression we use * *.*(..) to signify that we want to consider methods with any return type, within any class, with any name and taking any number and types of arguments. But we also use @io.mc.springaspects.UseAdviceOnThisMethod expression to point only to these methods that are annotated with @UseAdviceOnThisMethod.

Often in advice we want to access annotation to read some values from it. To access annotation we may modify our @Before advice to:

@Pointcut("execution(@io.mc.springaspects.UseAdviceOnThisMethod * *.*(..))")
protected void useAdviceOnThisMethodAnnotation() { }    

@Before("useAdviceOnThisMethodAnnotation() && @annotation(useAdvice)")
public void useAdviceBefore(JoinPoint joinPoint, 
                            UseAdviceOnThisMethod useAdvice) {
  consoleLogger.log("VALUE: %s", useAdvice.value());
}
Aspects and proxy unwrapping

Since Spring aspects are implemented using proxy classes we may skip advice invocation by unwrapping Spring bean:

DummyComponent dummyComponent = appContext.getBean(DummyComponent.class);
// advice called
dummyComponent.someMethod();
// unwrap target component - don't do this
// in real apps
dummyComponent = (DummyComponent) 
    ((Advised)dummyComponent).getTargetSource().getTarget();
// advice NOT called
dummyComponent.someMethod();
The End

We only scratched the surface of what Spring aspects can do, and we only used basic expressions in pointcut definitions. Aspect oriented programming is a huge topic and requires time and practice to master. I hope that this blog post helped you to understand what apects are and how to use them in Spring. If you have any remarks how I can improve this post please leave a comment.

DOWNLOAD SOURCE CODE

marcin-chwedczuk

A Programmer, A Geek, A Human