Implementing AOP in a Spring Application
20th Apr 2020 by Aneesh Mistry

Key Takeaways
• AOP enables us to separate cross-cutting concerns from within our application.
• Pointcut expressions enable fine-grained AOP implementations.
• Spring AOP makes use of light-weight AspectJ AOP classes and methods.

What is AOP?

AOP enables us to apply additional functionality to our application to separate cross-cutting concerns without updating the business logic source code.
A cross cutting concern, for example, may include security, logging, or analytics.

AOP introduces several keywords during its best-practice.
We will use the below example to describe a simple business logic application.

public class FootballGame {
	
	public void playGame() {
		System.out.println("Game has started.");
	}
}

The method playGame() is known as the Joinpoint. The Joinpoint is an exact point within the execution of an application that can be defined on the thread. An example of a Joinpoint can be calling a constructor or method, or throwing an Exception.
We are able to define different Joinpoints within our application by using Pointcut expressions. A Pointcut expression is a predicate that can match different Joinpoints. We define Pointcut expressions to pin-point certain Joinpoints within the application that we would like to apply our cross-cutting concerns to. If we defined a Pointcut expression for the above example, it would define the package, class name and method signature exactly.
In AOP, Advice is the action that is taken when a Pointcut expression is satisfied. Both the Pointcut expressions and the Advice are defined together within an Aspect class. The Aspect class is a handy way Spring can identify if, and where, to find possible AOP implementations to be applied in conjunction with a Joinpoint.
You may be wondering when, in relation to the Joinpoint execution, that the Advice method is called. The exact position in relation to the Joinpoint can be defined by the of 5 different types of Advice, illustrated below:

Advice in AOP

@Before is called before playGame() is executed.
@After is called after playGame() is executed.
@Around is called both before and after playGame() is executed.
@AfterReturning is called after playGame() is executed and returns a value.
@AfterThrowing is called after playGame() is executed, and if it throws an Exception.


Implementing AOP step by step

AOP requires two dependencies in our Spring project, we will define them in our POM.xml file:

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

We will be using the spring-context dependency for supporting Spring components and aspectj for supporting the Aspect class and Advice implementations.

public static void main(String[] args) {
		AnnotationConfigApplicationContext context
		= new AnnotationConfigApplicationContext(SpringConfig.class);

		FootballGame footballGame = context.getBean("footballGame", FootballGame.class);
		footballGame.playGame();
		context.close();
	}

We will begin by instructing the main thread to call a Joinpoint called playGame().
AnnotationConfigApplicationContext is used to import the Spring Configuration class that defines the package to scan for Spring components.
Context will be used to obtain a Spring bean to which it will later be used to call playGame().

The Spring configuration class exists as below:

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.aneesh.aopdemo")
public class SpringConfig {

}

The @Configuration annotation defines the class as a configuration class.
@EnableAspectJAutoProxy enables support for handling Aspect classes.
@ComponentScan("com.aneesh.aopdemo") directs the Spring application on where to search for components.

Business logic is processed in public class FootballGame. This class will begin with a single void method that prints "Game has started.".

@Component
public class FootballGame {

	public void playGame() {
		System.out.println("Game has started.");
	}
	
}

The above class includes the @Component annotation to ensure it is scanned by SpringConfig.

We will now build the Aspect class that will hold the Advice and Pointcut expressions to our business logic:

@Aspect
@Component
public class footballAspect {

	
}

The @Aspect annotation informs Spring that there may be Advice within this class to process.


Defining Pointcut expressions

Before we define the Advice, we need to understand which Joinpoint(s) we would like the Advice to be called upon.
When applying our Pointcut expressions, we use the term "execution". "execution" is the primary method of implementing Pointcut expressions in the following structure:
"execution([optional]{access modifier} {return type} {package} {class} {method name} {arguments}")
By default, the access modifier is 'Public', therefore it can be left undefined from the expression.
For each of the other input values, you can specify custom values or leave it open with an asterisk (*).

"execution(* com.aneesh.aopdemo.* .* () )"
Scan all public access modifiers, all return types for all classes within the com.aneesh.aopdemo package with all method names and no arguments

"execution(* com.aneesh.aopdemo.FootballGame .* () )"
Scan all public access modifiers, all return types for only the FootballGame class within the com.aneesh.aopdemo package with all method names and no arguments

"execution(* com.aneesh.aopdemo.FootballGame .playGame () )"
Scan all public access modifiers, all return types for only the FootballGame class within the com.aneesh.aopdemo package with the method name "playGame" and no arguments

"execution(* com.aneesh.aopdemo.FootballGame .playGame (..) )"
Scan all public access modifiers, all return types for only the FootballGame class within the com.aneesh.aopdemo package with the method name "playGame" and all possible arguments

"execution(private void com.aneesh.aopdemo.FootballGame .playGame (..) )"
Scan all private access modifiers, void return types for only the FootballGame class within the com.aneesh.aopdemo package with the method name "playGame" and all possible arguments

"execution(public void com.aneesh.aopdemo.FootballGame .playGame (String, int) )"
Scan all public access modifiers, void return types for only the FootballGame class within the com.aneesh.aopdemo package with the method name "playGame" and arguments of String followed by int

The examples above illustrate how specific or generic a Pointcut expression can be.
The granularity of Pointcut expressions enable us to define explicitly which Joinpoint(s) we want to be scanned.
We can then use the 5 Advice types to provide further granularity to specify where in the Joinpoint we would like to implement the Advice.
The below example will implement before and after Advice on our playGame() Joinpoint:


Using @Before and @After
@Aspect
@Component
public class footballAspect {

@Before("execution( void com.aneesh.aop.* .playGame (..))")
	public void beforeAspect() {
		
		System.out.println("Make sure players warm up before game.");
	}

@After("execution( void com.aneesh.aop.* .playGame (..))")
	public void afterAspect() {

		System.out.println("Make sure players warm down after a game.");
	}
}

The outcome produced from running the application:

Make sure players warm up before game.
Game has started.
Maintain hydration after the game.

The application has run the Before and After methods in their respective order as expected. As a result, the application has performed some additional behaviours beyond the business source code.

The @Around, @AfterReturning, and @AfterThrowing Advice are implemented slightly differently, introducing a few more variables.
To illustrate the annotations, we will introduce a new method, countPlayers(), that returns the number of players on the pitch if it is equal to 22, otherwise it will throw an Exception.
class FootballGame has been updated (below) and the main class will now call countPlayers() instead of playGame().

public class FootballGame {
	
	int playersOnPitch = 22;

	public void playGame() {
		System.out.println("Game has started.");
	}

	public int countPlayers() throws Exception{

		if (playersOnPitch == 22){
			System.out.println("FootballGame Class has successfully got 22 players.");
			return playersOnPitch;
		}
		else{
			System.out.println("FootballGame Class has not got 22 players and will throw Exception.");
			throw new Exception("FootballGame class Exception: Incorrect number of players.");
		}

	}	

}
public static void main(String[] args) {
		
		AnnotationConfigApplicationContext context
		= new AnnotationConfigApplicationContext(SpringConfig.class);

		FootballGame footballGame = context.getBean("footballGame", FootballGame.class);

		try {
			System.out.println("Main method has counted players value of: " + footballGame.countPlayers());
		}
		catch (Exception e){
			System.out.println("Main method has caught exception: " + e.getMessage());
		}

		context.close();
		
	}

Using @Around

We will create new Advice methods in the Aspect class to process the information from the new Joinpoint. We will include around the execution, and upon returning an int or Exception.

@Around( "execution(  int com.aneesh.aop.* .countPlayers (..))")
	public Object aroundAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

		System.out.println("Around: Count the players on the pitch...");

		Object result = proceedingJoinPoint.proceed();

		System.out.println("Around: Count is done.");
		return result;
	}

On line 1 we use the @Around type Advice. The Around Advice is very different to Before or After as it is intercepting the method call from the main method to countPlayers(). You will see on line 2 that the method returns an Object.
The business logic expects the call to countPlayers() to return an int. The Around Advice will intercept that method, but it now has the responsibility to retain the business logic and return an int.
The process of receiving, processing, and returning the int is achieved in 3 key sections: {line 4}, {line 6}, and {lines 8 & 9}.
Line 4 will be the business logic processed before countPlayers() is called.
Line 6 will use the ProceedingJoinPoint to execute the actual countPlayers() method.
ProceedingJoinPoint acts as the connection between the Advice and the countPlayers() Joinpoint. By calling .proceed(), we are telling the Advice to continue with executing the Joinpoint in the application.
proceedingJoinPoint.proceed() is a unique method to the @Around Advice as it sits on both sides of the Joinpoint.
By default, the .proceed() method will return an Object. We have defined an Object as the result on line 6, however this can be wrapped in an Integer and returned.
On lines 8 and 9, we process the business logic after the method has returned from countPlayers(). The int is then sent to the main application as it would have expected to be delivered had the Advice not been used. In this example, the int is packaged as an Object.


Using @AfterReturning

The below example uses @AfterReturning:

@AfterReturning(pointcut = "execution( int com.aneesh.aop.* .countPlayers (..))", returning="result")
	public void afterReturningAspect(int result)  {

		System.out.println("AfterReturning has the result: " + result);
	}

On Line 1 we use the @AfterReturning Advice to process information after countPlayers() has returned an Object (otherwise an int).
The argument to the Advice, however, does not only consist of a Pointcut expression. We define the Pointcut to the variable "pointcut", then we also define a String called "return" to the "returning" property.
The value of "returning" is used in the Advice method. The "returning" value is equal to what is returned from countPlayers(). The "returning" value is later back to the main method after the Advice.
We could use the result from countPlayers() in the Advice and return it to the application by adjusting the method signature (on line 2) and returning an int at the end of the method (line 5), however this example will only return void.
On line 2 we can see the method will take an int argument which comes directly from countPlayers(). The business logic is processed on line 4.
The diagram below illustrates the movement of Objects between the countPlayers() and the Advice, before being returned to the main method:

AfterReturning Advice flow

The result from processing the AfterReturning Advice would be:

Around: Count the players on the pitch...
FootballGame Class has successfully got 22 players.
Around: Count is done.
AfterReturning has the result: 22
Main method has counted players value of: 22

Using @AfterThrowing

The current application does not throw an Exception, but if we were to change the playersOnPitch value in class FootballGame, countPlayers() would throw an Exception.
The example below demonstrates how an Exception would be handled with Advice.

@AfterThrowing(pointcut = "execution( int com.aneesh.aop.* .countPlayers (..))",
				 throwing = "thrownExpression")
	public void afterThrowingAspect(Throwable thrownExpression)  {

		System.out.println("AfterThrowing has received a message: " + thrownExpression.getMessage());
	}

On line 2 we have defined the Advice in a similar pattern to the AfterReturning Advice. Instead of using the "returning" parameter, we are using "throwing", and we pass the Exception into the Advice method as a Throwable on line 3.
On line 5, we simply process the message of the Exception before allowing the Exception to be sent back to the main method.
In real business logic, we may log the Exception or transform it before sending it back.

If we change the number of players so an Exception is thrown, the AfterThrowing Advice would return:

Around: Count the players on the pitch...
FootballGame Class has not got 22 players and will throw Exception.
AfterThrowing has received a message: FootballGame class Exception: Incorrect number of players.
Main method has caught exception: FootballGame class Exception: Incorrect number of players.

Notice that the second Around Advice has not been called as an Object was not returned to the Advice. Instead, the thread has thrown an Exception.
The AfterThrowing Advice is acknowledged when the Exception is thrown and run before the Exception is sent back to the main method.


Conclusion

Spring provides simple and effective tools that enable us to implement AOP into our applications.
By defining Pointcut expressions, we are able to specify exact Joinpoints where we would like to add additional processing. We use Advice types to define when we would like the processing to happen, and as a result, we are able to delicately add additional functionality to our application.
By using AOP with object-orientated programming, we are able to drastically improve the maintainability of our source code by grouping additional features and behaviours of our application into modular sections.
Furthermore, we are able to cross boundaries that are defined by packages and classes within our application to provide shared behaviour and functionality that can, incredibly, be defined by just a single method.


Picture: Rio De Janeiro, Brazil by Raphael Nogueira


Share this post