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:
- Part 1 – concepts and terminology
- Part 2 – Implementing simple AOP using the Spring AOP
@Aspect
annotation.
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. TheObject[]
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 aString
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 abstract
, final
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.