Aspect-Oriented Programming (Spring AOP)
In this article , we will talk about aspect oriented programming programming (AOP) definition and implement it using springboot framework .
What is it?
- An aspect is a common feature that's typically scattered across methods, classes, object hierarchies, or even entire object models. It is behavior that looks and smells like it should have structure, but you can't find a way to express this structure in code with traditional object-oriented techniques.
- In AOP, a feature like metrics is called a crosscutting concern, as it's a behavior that "cuts" across multiple points in your object models, yet is distinctly different. As a development methodology, AOP recommends that you abstract and encapsulate crosscutting concerns.
For example, let's say you wanted to add code to an application to measure the amount of time it would take to invoke a particular method. In plain Java, the code would look something like the following.
public class BankAccountDAO
{
public void withdraw(double amount)
{
long startTime = System.currentTimeMillis();
try
{
// Actual method body...
}
finally
{
long endTime = System.currentTimeMillis() - startTime;
System.out.println("withdraw took: " + endTime);
}
}
}
While this code works, there are a few problems with this approach:
- It's extremely difficult to turn metrics on and off, as you have to manually add the code in the try>/finally block to each and every method or constructor you want to benchmark.
- The profiling code really doesn't belong sprinkled throughout your application code. It makes your code bloated and harder to read, as you have to enclose the timings within a try/finally block.
- If you wanted to expand this functionality to include a method or failure count, or even to register these statistics to a more sophisticated reporting mechanism, you'd have to modify a lot of different files (again).
OOP may not always be the best way to add metrics to a class.
Aspect-oriented programming gives you a way to encapsulate this type of behavior functionality. It allows you to add behavior such as metrics "around" your code. For example, AOP provides you with programmatic control to specify that you want calls to BankAccountDAO to go through a metrics aspect before executing the actual body of that code.
In short, all AOP frameworks define two things: a way to implement crosscutting concerns, and a programmatic construct -- a programming language or a set of tags -- to specify how you want to apply those snippets of code.
One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP, meaning you do not need to use AOP if you don't want to, AOP complements Spring IoC to provide a very capable middleware solution.
AOP is used in the Spring Framework to...
- ... provide declarative enterprise services, especially as a replacement for EJB declarative services. The most important such service is declarative transaction management.
- ... allow users to implement custom aspects, complementing their use of OOP with AOP.
Spring AOP capabilities and goals
- Spring AOP is implemented in pure Java. There is no need for a special compilation process. Spring AOP does not need to control the class loader hierarchy, and is thus suitable for use in a J2EE web container or application server.
- Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans). Field interception is not implemented, although support for field interception could be added without breaking the core Spring AOP APIs. If you need to advise field access and update join points, consider a language such as AspectJ.
- Spring AOP's approach to AOP differs from that of most other AOP frameworks. The aim is not to provide the most complete AOP implementation (although Spring AOP is quite capable); it is rather to provide a close integration between AOP implementation and Spring IoC to help solve common problems in enterprise applications.
Example
Applications are generally developed with multiple layers. A typical Java application has
- Web Layer - Exposing the services to outside world using REST or a web application
- Business Layer - Business Logic
- Data Layer - Persistence Logic
While the responsibilities of each of these layers are different, there are a few common aspects that apply to all layers
- Logging
- Security
These common aspects are called Cross Cutting Concerns.
One option of implementing cross cutting concerns is to implement it seperately in every layer. However, that would mean that the code becomes difficult to maintain.
Setting up Spring Boot AOP Project
Creating a Spring AOP Project with Spring Initializr is a cake walk.
Spring Initializr https://start.spring.io/ is great tool to bootstrap your Spring Boot projects.
Setting up AOP Example
Let’s add a couple of business classes Business1 and Business2. These business classes are dependent on a couple of data classes Data1 and Data2.
@Service
public class Business1 {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private Dao1 dao1;
public String calculateSomething(){
String value = dao1.retrieveSomething();
logger.info("In Business - {}", value);
return value;
}
}
@Service
public class Business2 {
@Autowired
private Dao2 dao2;
public String calculateSomething(){
//Business Logic
return dao2.retrieveSomething();
}
}
@Repository
public class Dao1 {
public String retrieveSomething(){
return "Dao1";
}
}
@Repository
public class Dao2 {
public String retrieveSomething(){
return "Dao2";
}
}
Notes
- @Autowired private Dao1 dao1 - The Dao’s are autowired as dependencies into the Business classes.
- public String calculateSomething(){ - Each of the business classes have a simple calculate method.
Simple Unit Test to Test AOP
Let’s write a simple unit test to invoke the business classes created below.
@RunWith(SpringRunner.class)
@SpringBootTest
public class BusinessAopSpringBootTest {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private Business1 business1;
@Autowired
private Business2 business2;
@Test
public void invokeAOPStuff() {
logger.info(business1.calculateSomething());
logger.info(business2.calculateSomething());
}
}
At this point, we have no AOP logic implemented. So, the output would be the simple messages from the Dao and Business classes.
c.i.s.t.b.e.a.BusinessAopSpringBootTest : In Business - Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1
Implementing @Before advice
Typically when we implement security using AOP, we would want to intercept the call to the method and apply your check. This is typically done using @Before advice.
An implementation is shown below.
@Aspect
@Configuration
public class UserAccessAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//What kind of method calls I would intercept
//execution(* PACKAGE.*.*(..))
//Weaving & Weaver
@Before("execution(* com.in28minutes.springboot.tutorial.basics
.example.aop.data.*.*(..))")
public void before(JoinPoint joinPoint){
//Advice
logger.info(" Check for user access ");
logger.info(" Allowed execution for {}", joinPoint);
}
}
Notes
- @Aspect - Indicates that this is an Aspect
- @Configuration - Indicates that this file contains Spring Bean Configuration for an Aspect.
- @Before - We would want to execute the Aspect before execution of the method
- ("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))") - This defines the point cut - We would want to intercept all method calls made to any methods in the package com.in28minutes.springboot.tutorial.basics.example.aop.data
When you run the unit test, you will see that before executing the DAO method the user access check code is executed.
Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething()) c.i.s.t.b.e.a.BusinessAopSpringBootTest : In Business - Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1 Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething()) c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao2
Understanding AOP Terminology - Pointcut, Advice, Aspect, Join Point
Let’s spend some time understanding the AOP Terminology
- Pointcut - Pointcut is the expression used to define when a call to a method should be intercepted. In the above example, execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..)) is the pointcut.
- Advice - What do you want to do? It is the logic that you would want to invoke when you intercept a method. In the above example, it is the code inside the before(JoinPoint joinPoint) method.
- Aspect - A combination of defining when you want to intercept a method call (Pointcut) and what to do (Advice) is called an Aspect.
- Join Point - When the code is executed and the condition for pointcut is met, the advice is executed. The Join Point is a specific execution instance of an advice.
- Weaver - Weaver is the framework which implements AOP - AspectJ or Spring AOP.
Let’s now the other interception options AOP provides.
- @After - Executed in two situations - when a method executes successfully or it throws an exception.
- @AfterReturning - Executed only when a method executes successfully.
- @AfterThrowing - Executed only when a method throws an exception.
Let’s create a simple Aspect with a couple of these variations.
@Aspect
@Configuration
public class AfterAopAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@AfterReturning(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))",
returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
logger.info("{} returned with value {}", joinPoint, result);
}
@After(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))")
public void after(JoinPoint joinPoint) {
logger.info("after execution of {}", joinPoint);
}
}
The code is self explanatory.
Output from execution is shown below:
Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething()) In Business - Dao1 after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething()) execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething()) returned with value Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1 Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething()) after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething()) execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething()) returned with value Dao2 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao2
Other AOP Features - @Around and Annotations
One of the other features you can implement using AOP are custom annotations for intercepting method calls.
Example below shows a simple TrackTime annotation.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {
We can add an aspect defining what should be done when TrackTime annotation is used. MethodExecutionCalculationAspect implements a simple time tracking.
@Aspect
@Configuration
public class MethodExecutionCalculationAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Around("@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime)")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long timeTaken = System.currentTimeMillis() - startTime;
logger.info("Time Taken by {} is {}", joinPoint, timeTaken);
}
}
Notes
- @Around - Uses an around advice. Intercepts the method call and uses joinPoint.proceed() to execute the method.
- @annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime)- Pointcut to define interception based on an annotation - @annotation followed by the complete type name of the annotation.
Once we define the annotation and the advice, we can use the annotation on methods which we would want to track as shown below.
@Service
public class Business1 {
@TrackTime
public String calculateSomething(){
AOP Best Practices
One of the AOP Best Practices is to deine a Common Class to store all the Pointcuts. This helps in maintaining the pointcuts at one place.
public class CommonJoinPointConfig {
@Pointcut("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))")
public void dataLayerExecution(){}
@Pointcut("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
public void businessLayerExecution(){}
}
The above common definition can be used when defining point cuts in other aspects.
@Around("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")