Spring AOP pointcut advice example
Introduction
Spring AOP provides a powerful and flexible pointcut and advice configuration. We have seen previously how to configure Spring AOP advices in the following article: Spring AOP example.
In that previous article we also covered the available advice types:
- Before
- AfterReturning
- AfterThrowing
- After
- Around
In this article we will see how to configure pointcuts independently of advices and also cover some available features like passing parameters to advice methods, preparing parameters before sending them to service methods, etc.
In case you are not familiar with the basics of Spring AOP, that previous article may be useful: It covers Spring AOP from the beginning.
This tutorial considers the following environment:
- Ubuntu 12.04
- JDK 1.7.0.21
- Spring 3.2.5
- AspectJ 1.7.4
The Spring service
Following next is a simple Spring service that will be used as an example through the article:
package com.byteslounge.service; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; import com.byteslounge.model.Account; @Service public class ExampleService { public void updateAccountBalance(Account account, Long amount) { System.out.println("Inside updateAccountBalance(). Account: " + account.getAccountNumber() + ", amount: " + amount); } public List<Account> findCustomerAccounts(Long customerId) { System.out.println("Finding accounts for customer: " + customerId); List<Account> result = new ArrayList<>(); result.add(new Account("000001", "Account 1")); return result; } public void updateAccountDescription(Account account) { System.out.println("Updating account description to :" + account.getAccountDescription()); } public <T> void methodUsingGenerics(T parameter) { System.out.println("Inside methodUsingGenerics: " + parameter.getClass().getName()); } }
We just defined some dummy methods which execution will be intercepted by the AOP pointcuts and advices we will configure through the article.
The AOP pointcuts
In a complex enterprise application we usually define the pointcuts in both centralized and layered style:
package com.byteslounge.spring.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class PointcutDefinition { @Pointcut("within(com.byteslounge.web..*)") public void webLayer() { } @Pointcut("within(com.byteslounge.service..*)") public void serviceLayer() { } @Pointcut("within(com.byteslounge.dao..*)") public void dataAccessLayer() { } }
In order to keep this article simple and focused we defined only three illustrative pointcuts: Each of the pointcuts will be associated with the web, service and DAO layer depending on the package containing the methods we will intercept.
Note the Pointcut annotations: They contain an expression that will be matched against our application Spring bean packages, according to the layer they represent.
Pointcuts are used in advice matchers by referring to them in the following way:
com.byteslounge.spring.aop.PointcutDefinition.serviceLayer()
This expression represents the service layer pointcut we just defined. Each pointcut is referenced by its fully qualified name.
In the following sections we will define advices that will be used - together with the pointcuts - to intercept Spring bean methods.
Passing parameters to AOP advices
In Spring AOP it is possible to pass parameters from the intercepted method into the advice.
Let's define an advice:
@Aspect public class AccountLoggingAspect { @Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && " + "args(account,..)") public void beforeAccountMethodExecution(Account account) { System.out.println("Logging account access. Account: " + account.getAccountNumber()); } }
This class - AccountLoggingAspect - will be used to define advice methods. We will only show a single advice method per example to keep things clearer. In practice you would want to define multiple and related advice methods in a single class.
The advice we just defined will intercept all calls to any Spring bean method defined in the service layer (note the advice expression referencing our service layer pointcut), which in turn takes at least a parameter of type Account. The account parameter should be the first parameter (note the args expression).
If we happen to hold a reference to the service ExampleService and execute the following method:
Account account = new Account("000001", "Account 1"); exampleService.updateAccountBalance(account, 100L);
The following output will be generated:
Inside updateAccountBalance(). Account: 000001, amount: 100
Now we may change our advice method signature to the following include the JoinPoint:
@Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && " + "args(account,..)") public void beforeAccountMethodExecution(JoinPoint jp, Account account) { System.out.println("Before method: " + jp.getSignature().getName() + ". Class: " + jp.getTarget().getClass().getSimpleName()); System.out.println("Logging account access. Account: " + account.getAccountNumber()); }
The JoinPoint will be injected by the Spring container and may be used to extract information from the method being intercepted. If we execute the same ExampleService service method as before - updateAccountBalance() - the following output will be generated:
Logging account access. Account: 000001
Inside updateAccountBalance(). Account: 000001, amount: 100
Since method updateAccountBalance() from our Spring service takes two parameters - account and amount - we may change the advice definition and signature to the following and capture both parameters:
@Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && " + "args(account,amount)") public void beforeAccountMethodExecution(JoinPoint jp, Account account, Long amount) { System.out.println("Logging account access. Account: " + account.getAccountNumber() + ", Amount: " + amount); }
Note that we changed both the method signature and the args value in the advice definition.
Using annotations in AOP advices
We may also use annotations in advices in order to to match intercepted methods. Let's define an annotation:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditDestination value(); public enum AuditDestination { DATABASE, FILE_SYSTEM; }; }
And the following advice:
@Aspect public class AccountLoggingAspect { @Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && " + "args(account,..) && @annotation(auditable)") public void audit(Account account, Auditable auditable) { System.out.println("Audit account access: " + account.getAccountNumber() + ". Audit destination: " + auditable.value()); } }
The advice is now also expecting the intercepted method to have the @Auditable annotation. If we go to the updateAccountBalance() method from our Spring service and add the annotation:
@Auditable(AuditDestination.DATABASE) public void updateAccountBalance(Account account, Long amount) { System.out.println("Inside updateAccountBalance(). Account: " + account.getAccountNumber() + ", amount: " + amount); }
Executions of this Spring bean method will be intercepted by the AOP advice since it will match the @annotation(auditable) expression.
Note that the annotation itself may also be passed into the advice method (auditable parameter) and used to process the desired business logic.
AOP advice intercepted method name and return type
We may also define advices that are based on the intercepted method return type and/or method name.
Consider the following advice:
@Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && " + "execution(java.util.List<com.byteslounge.model.Account> find*(..)) && " + "args(customerId,..)") public void beforeAcountFinder(Long customerId) { System.out.println("Logging access to account finder. CustomerID: " + customerId); }
This advice will target methods in the service layer which:
- Return type is List<Account>
- Method name starts with find
- The first method parameter name is customerId with Long type
If we hold a reference to our example service bean and call:
List<Account> accounts = exampleService.findCustomerAccounts(1L);
The following output will be generated:
Finding accounts for customer: 1
Changing intercepted method parameters in AOP advices
We may use advices to change - or prepare - parameters passed into intercepted methods. Consider the following advice:
@Around(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && " + "execution(* com.byteslounge.service.ExampleService.updateAccountDescription(..)) && " + "args(account)") public void beforeUpdateAccountDescription(ProceedingJoinPoint pjp, Account account) throws Throwable { account.setAccountDescription(account.getAccountDescription() .toUpperCase()); System.out .println("Logging access to updateAccountDescription. Prepared account description: " + account.getAccountDescription()); pjp.proceed(new Object[] { account }); }
This time we are intercepting calls that are made specifically to the updateAccountDescription() method of our service bean (note the advice definition).
The advice is intercepting the service method call and normalizing the account description to upper case. After normalization it will pass the recently changed Account instance to the intercepted bean method and execution will proceed.
AOP advices with generic types
It is also possible to configure advices to intercept methods with parameters of generic types. We may define an advice that will only intercept a method if a generic parameter is of a given type:
@Aspect public class AccountLoggingAspect { @Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && " + "execution(* com.byteslounge.service.ExampleService.methodUsingGenerics(..)) && " + "args(parameter)") public void beforeGenericMethod(Account parameter) { System.out.println("Logging access to generic method. Account: " + parameter.getAccountNumber()); } }
This advice will intercept method methodUsingGenerics() of our example service. Additionally it only intercept calls made to this method if the parameter is of type Account.
Let's remember the method we defined earlier in the service:
public <T> void methodUsingGenerics(T parameter) { System.out.println("Inside methodUsingGenerics: " + parameter.getClass().getName()); }
If parameter is of type Account our advice will intercept it as expected.
Downloadable sample
You may find a downloadable sample at the end of this page that covers all of the aforementioned scenarios.
Further reading
We covered some of the features provided by Spring AOP pointcuts and advices. The official Spring documentation contains additional details on AOP and it should definitely be read if you tend to use it in your Spring application.