Aspect-Oriented Programming – Part 3

Aspect-Oriented Programming (AOP) diagram

This is the last part in my 3-part series on Aspect-Oriented Programming in Java.

If you missed the previous posts, here are the links:

Last week I left out all the Spring container details because I just wanted to focus on the actual aspect code.

This week I’ll implement around advice using both Spring AOP and the JEE/EJB AOP using the Interceptor API.

AOP Around Advice Example

The two implementations are different. We can write all the usual advice (before, after, after-returning, after-throwing and around) in Spring AOP. The Interceptor API only allows us to write around advice with the @AroundInvoke annotation. However, it also allows us to intercept constructors with @AroundConstruct and timeouts with @AroundTimeout.

Spring AOP @Around Annotation

Last week we looked at the @Before@AfterReturning, etc. advice. What about the @Around advice? This is the most powerful advice because it wraps around a join point. We then have access to the parameters, return value and thrown exceptions of that join point, and can even choose not to call the join point.

The best practice is to use the least powerful form of advice that meets the requirements. Don’t use @Around advice if a simpler form of advice will do.

The heart of the @Around advice is the ProceedingJoinPoint parameter. This must be the first parameter of the @Around advice method. Using ProceedingJoinPoint and its superclass JoinPoint, we can access the parameters, return value and thrown exceptions of the join point. We can even decide whether to call the target method or not.

The ProceedingJoinPoint interface allows us to access just about anything we ever need to know about a particular join point, including:

  • Object proceed() — proceeds with the next advice or target method invocation.
  • Object proceed(Object[] args) — proceeds with the next advice or target method invocation. The Object[] arguments must be the same number and in the same order as the advice signature.

Calling proceed() on the ProceedingJoinPoint parameter will execute the target method. The proceed() method returns an Object which represents the returned value from the target method. This can be used, inspected, and changed if needed.

The base JoinPoint interface includes the following methods:

  • Object[] getArgs() — returns the arguments at this join point.
  • String getKind() — returns a String representing the kind of join point.
  • Signature getSignature() — returns the signature at the join point.
  • Object getTarget() — returns the target object.
  • String toString()— returns the description of the method being advised.

We can access the arguments passed to the target method with the getArgs() method. It returns an Object[] which can be changed if necessary. The modified Object[] can be passed to the overloaded proceed() method which in turn passes it as the target method arguments.

Interestingly, any of the previous advice (@Before@After, etc.) methods can declare a first parameter with the JoinPoint type.

Spring AOP @Around Example

Here is a simple @Around advice which times the tackle() method call:

package com.office;

import java.io.PrintStream;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AOPTimer {

    private PrintStream stream;

    public AOPTimer(PrintStream stream) {
        this.stream = stream;
    }

    // Same pointcut designator expression as before
    @Pointcut ("execution(* *.tackle(..))")
    public void tackleTask() {}

    @Around("tackleTask()")
    public void timeMethodCall(ProceedingJoinPoint pjp) 
                                     throws Throwable {
        long start, end;
        start = System.nanoTime();
        try {
            pjp.proceed();
        }
        catch (Throwable t) {
            // Exception can be ignored, changed or rethrown
            // It can even be wrapped in another exception
            throw t;
        }
        finally {
            end = System.nanoTime();
            stream.printf(
                   "Method call %s took %,d nanoseconds%n", 
                   pjp.getSignature(), end-start);
        }
    }
} // end of class

At runtime, the Spring container will create a proxied class that will intercept the execution of the tackle() method of the Programmer class, and run the timeMethodCall() method of the AOPTimer.

Interceptor API @AroundInvoke Annotation

The Interceptor API @AroundInvoke annotation is very similar to Spring’s @Around annotation, but has a slightly different implementation.

We use the @AroundInvoke annotation to annotate any method in an interceptor class. The method can have any name. It must take a single parameter of type InvocationContext and have a return type of Object. The method must not be abstractfinal or static.

The InvocationContext parameter is used in the same way as the previous ProceedingJoinPoint. It represents the invoked business method. The InvocationContext also has a proceed() method to invoke the target method. We can get and set the parameters, return value and thrown exceptions of the join point, and even decide whether or not to call the target method.

A list of some of the more useful InvocationContext methods follows:

  • Object getTarget() returns a reference to the target bean.
  • Method getMethod() returns the invoked method of the target bean.
  • Object[] getParameters() returns the parameters passed to the method.
  • void setParameters() modifies the parameters passed to the method.
  • Object proceed() calls the next interceptor if there is one, or calls the target bean method.

Interceptor API @AroundInvoke Example

Here is a simple example of an interceptor with an @AroundInvoke annotated method:

package com.office.interceptor;

import javax.interceptor.*;

public class InvocationTimer {
    @AroundInvoke
    public Object timeMethodCall(InvocationContext invocation) 
                                             throws Exception {
        long start, end;
        start = System.nanoTime();
        try {
            return invocation.proceed();
        }
        finally {
            end = System.nanoTime();
            System.out.printf(
                       "Method call %s took %,d nanoseconds%n", 
                       invocation.getMethod(), end-start);
        }
    } // end of interceptor method
} // end of class

The proceed() method is the heart of the interceptor. It does the actual call to the bean business method that the client is calling. Because the timeMethodCall() method is invoked in the same Java call stack as the business method, proceed() must be called by the interceptor code, or the actual target method will not be called. If another interceptor must be invoked as part of the method call, then proceed() calls the @AroundInvoke method of that other interceptor. If no other interceptors need to be executed, then the EJB container calls the actual bean method.

To apply interceptors, the @Interceptors annotation is used on either on an entire bean or individual methods of a bean.

Applying the @Interceptors annotation to an individual method, the interceptor is executed only when that particular method is called:

@Interceptors(InvocationTimer.class)
public void foo(int bar) {
    ...
}

Using the @Interceptors annotation at the class level, all interceptors will intercept every method call of every business method of the EJB:

@Stateful
@Interceptors({InvocationTimer.class, AnotherInterceptor.class})
public class SomeBean {
    public void foo() { ... }
    public void bar() { ... }
    public void baz() { ... }
    ...
}

The @Interceptors annotation is very easy to use. However, it forces us to recompile classes if and when we change or disable interceptors. Configuring the intercepted classes in XML would probably then be a better approach.

Conclusion

This ends my introduction to AOP theory and practice, and some different AOP implementations. If you’d like to dive deeper, please join me on one of our Spring and/or EJB courses.

Please share your views and comments.

Leave a Comment

Your email address will not be published. Required fields are marked *

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.