Spring AOP example
Introduction
One of the main features of the Spring Framework is the Aspect Oriented Programming (AOP) paradigm. It's main purpose is to aggregate sections of code that would be usually repeated in multiple places of your application in a single spot, for example boilerplate code to open-commit-rollback a transaction.
By removing boilerplate code from your business methods you are making them cleaner. Another example of such common use case for AOP is to log - or audit - calls to some (or all) business methods.
In this article we will implement a simple logging aspect that will intercept calls to a Spring service. Although Spring supports aspect configuration via XML we will configure our aspect using Spring supported AspectJ annotations.
This tutorial considers the following environment:
- Ubuntu 12.04
- JDK 1.7.0.21
- Spring 3.2.5
- AspectJ 1.7.4
The following Maven dependencies are required:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>3.2.5.RELEASE</spring.version> <aspectj.version>1.7.4</aspectj.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> </dependencies>
The Spring service
Following next is a very simple Spring service that will be used in this example:
package com.byteslounge.spring.service; import org.springframework.stereotype.Service; @Service public class ExampleService { public void simpleMethod() { System.out.println("Inside simpleMethod"); } public Object methodReturnsValue() { System.out.println("Inside methodReturnsValue"); return new String("Hello from methodReturnsValue"); } public void methodThrowsException() { System.out.println("Inside methodThrowsException"); throw new RuntimeException("Exception from methodThrowsException"); } public Object testAroundReturningResult() { System.out.println("Inside testAroundReturningResult"); return new String("Hello from aroundReturningResult"); } public void testAroundThrowingException() throws Exception { System.out.println("Inside testAroundThrowingException"); throw new RuntimeException("Exception from testAroundThrowingException"); } }
Before AOP advice
The first Spring supported AOP advice we will see is the Before advice. As the name states this AOP advice will be called before intercepted methods are executed.
package com.byteslounge.spring.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class LoggingAspect { @Before( "execution(* com.byteslounge.spring.service.ExampleService.simpleMethod(..))" ) public void beforeExecution(JoinPoint jp) { System.out.println("Before method: " + jp.getSignature().getName() + ". Class: " + jp.getTarget().getClass().getSimpleName()); } }
By annotating a class with @Aspect we are defining that class as an Aspect. An Aspect usually groups related advices. In our example our aspect will define all advices related with logging.
The method we just defined inside our aspect - beforeExecution() - is annotated with @Before. This annotation contains a pointcut that determines the advice to be executed before simpleMethod from the Spring service we defined earlier gets called.
The JoinPoint parameter passed into the advice contains all the information about the Service method being intercepted.
If we happen to hold a reference to our service and call simpleMethod:
exampleBean.simpleMethod();
The following output will be generated:
Inside simpleMethod
AfterReturning AOP advice
AfterReturning advice will be called if the method being intercepted returns a value without throwing an exception.
package com.byteslounge.spring.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; @Aspect public class LoggingAspect { @AfterReturning( pointcut = "execution(* com.byteslounge.spring.service.ExampleService.methodReturnsValue(..))", returning = "result" ) public void afterReturningExecution(JoinPoint jp, Object result) { System.out.println("After returning method: " + jp.getSignature().getName() + ". Class: " + jp.getTarget().getClass().getSimpleName()); System.out.println("Result returned: " + result); } }
A new configuration parameter introduced in this advice pointcut is the returning value defined in @AfterReturning annotation.
This parameter value must have an exact match with the name of an argument of the advice method. The argument passed into the advice will be the return value of the intercepted Service method. Keep in mind that the advice method argument and the result of the intercepted service method must be of the same type (in this example the type is Object).
If we happen to hold a reference to our service and call methodReturnsValue:
Object result = exampleBean.methodReturnsValue();
The following output will be generated:
After returning method: methodReturnsValue. Class: ExampleService
Result returned: Hello from methodReturnsValue
AfterThrowing AOP advice
AfterThrowing advice will be called if the method being intercepted throws an exception.
package com.byteslounge.spring.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; @Aspect public class LoggingAspect { @AfterThrowing( pointcut = "execution(* com.byteslounge.spring.service.ExampleService.methodThrowsException(..))", throwing = "ex" ) public void afterThrowingExecution(JoinPoint jp, Exception ex) { System.out.println("After throwing method: " + jp.getSignature().getName() + ". Class: " + jp.getTarget().getClass().getSimpleName()); System.out.println("Exception: " + ex.getMessage()); } }
Once again we have a new parameter in the advice pointcut: The throwing parameter.
This parameter value must have an exact match with the name of an argument of the advice method. The argument passed into the advice will be the exception thrown by the intercepted Service method.
If we happen to hold a reference to our service and call methodThrowsException:
exampleBean.methodThrowsException();
The following output will be generated:
After throwing method: methodThrowsException. Class: ExampleService
Exception: Exception from methodThrowsException
After AOP advice
After advice will be called when the method being intercepted exits either returning a value or throwing an exception. Because of this behaviour this advice is also known as finally (analogy with the finally statement in a try-catch-finally block).
This kind of advice is useful for releasing or clean up previously acquired resources.
package com.byteslounge.spring.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; @Aspect public class LoggingAspect { @After( "execution(* com.byteslounge.spring.service.ExampleService.simpleMethod(..))" ) public void afterExecution(JoinPoint jp) { System.out.println("After method: " + jp.getSignature().getName() + ". Class: " + jp.getTarget().getClass().getSimpleName()); } }
If we happen to hold a reference to our service and call simpleMethod:
exampleBean.simpleMethod();
The following output will be generated:
After method: simpleMethod. Class: ExampleService
Around AOP advice
Around advice is the most powerful of all spring AspectJ supported advices.
This advice resembles an EJB interceptor. You have the possibility to execute code both before and after the intercepted method is called and even return a result to the caller instead of executing the intercepted method.
Suppose your intercepted service method would fetch a result from the database. You could check if the result to be fetched is stored in some kind of cache and return the cached value instead of executing the intercepted service method.
You may also catch unexpected exceptions from the intercepted method and wrap them in an exception that the caller is prepared to handle.
package com.byteslounge.spring.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class LoggingAspect { @Around( "execution(* com.byteslounge.spring.service.ExampleService.testAround*(..))" ) public Object aroundExecution(ProceedingJoinPoint jp) throws Exception { System.out.println("Before method: " + jp.getSignature().getName() + ". Class: " + jp.getTarget().getClass().getSimpleName()); try { // Proceed with method invocation Object result = jp.proceed(); System.out.println("Returning: " + result); return result; } catch (Throwable e) { // Log error System.out.println("Error: " + e.getMessage()); // Throw exception to the caller throw new Exception("Error", e); } } }
If we happen to hold a reference to our service and call testAroundReturningResult:
Object result = exampleBean.testAroundReturningResult();
The following output will be generated:
Inside testAroundReturningResult
Returning: Hello from aroundReturningResult
If we call testAroundThrowingException instead:
exampleBean.testAroundThrowingException();
The following output will be generated:
Inside testAroundThrowingException
Error: Exception from testAroundThrowingException
Spring configuration file
Finally the Spring configuration file used in this example:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.byteslounge.spring" /> <aop:aspectj-autoproxy /> <bean id="loggingAspect" class="com.byteslounge.spring.aop.LoggingAspect"> </bean> </beans>
We are using the usual package component scan because the Spring service used in this example is configured using @Service annotation and is inside package com.byteslounge.spring.
aspectj-autoproxy element is required for enabling Spring AOP support.
Bean loggingAspect is defining our Aspect as a Spring managed bean. Since our Aspect class is also inside package com.byteslounge.spring we could have omitted this bean XML definition and rely on package scanning.
To do this we would have to include another Spring annotation in our Aspect class since the @Aspect annotation itself is not enough for Spring component scan detection (use for example @Component Spring annotation):
package com.byteslounge.spring.aop; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component @Aspect public class LoggingAspect { }
A fully working sample covering all the described scenarios is available for download at the bottom of this page.