In this article we will determine what Aspect Oriented Programming (AOP) is, how and when to use it. We will also learn how Spring framework creates and uses AOP, why this concept is a core one for Spring.
I think this article might be interesting for both audiences: “newcomers” who just recently started learning Spring or using it as well as experienced engineers, who have already built some applications with the Spring framework without going deep into implementation.
Aspect Oriented Programming is a paradigm that is complementary to Object Oriented Programming (OOP). While OOP arranges code into objects and methods, AOP deals with aspects, or parts of the program, that are not dependent on the objects. You may often face the following statement regarding AOP:
AOP addresses the problem of cross-cutting concerns
But what does it mean? Trying to simplify as much as possible: when we add AOP, we are wrapping our code with some new functionality. Let’s consider the most often use cases for AOP as it may become more clear how and when to use it:
For instance:
Logging: We all know what logging is and why we need it, right? And I’m sure every one of you had that experience writing similar log lines for example at the beginning of your method. Something like:
log.info("Executing method someMethodName with following params {} {} {}".......)
If you need to capture details about method execution across various classes or let’s say you are building an e-commerce application and you want to a log purchase. This is where AOP comes to help you. Instead of copy-pasting your log lines across all the methods, you may just create one aspect and apply it to all methods you need across the whole application.
Security: Ensuring sensitive methods are accessible only to authorized users. Are you developing a banking application and don’t want all users to be able to see each other's account balances and all other data? Do you think you have to put in role checking in every method? Actually, no. You may implement role-based access control with AOP.
Performance Metrics: AOP might be useful for tracking the execution time of methods to identify bottlenecks or build dashboards. For applications that deal with a lot of requests, for example, streaming services or real-time analytics dashboards, it is beneficial to know how long each request took to be processed. AOP can help measure and log the execution time of all methods so that optimizations can be done quickly. These tasks are therefore abstracted away from the developers by AOP, such that they can focus on developing business logic without having to create boilerplate code.
Without AOP, you would have to copy paste millions lines of repetitive and intrusive code in every single method. On the flip side - with AOP, you can extract these concerns into one place and apply them dynamically where you need them.
In Spring, AOP often works hand-in-hand with annotations, making it almost magical.
For instance:
The @Transactional
annotation. Oh yeah, every single time you add this annotation you add a new aspect to your code. The @Aspect
annotation. Can you guess what it does? Exactly! It lets you define custom aspects.
And you may ask - How does spring achieve it? It uses proxies under the hood — dynamic classes that intercept method calls and inject additional behaviors whatever you need aspects for. For example, when you annotate a method with @Transactional
, Spring’s AOP intercepts the method call, begins a transaction, calls the method, and commits or rolls back the transaction depending on the outcome. This process is seamless to the developer but incredibly powerful. Just one word and so many actions under the hood. But you may say @Transactional
is great. But what if I want to create my own Aspect, what should I know? So, I think it’s time to dive into the foundational concepts of AOP and understand what the usual concept consists of.
Let’s sum up: An aspect is a module that is a collection of cross-cut concerns such as logging, security transactions, or management. An aspect can be thought of as some helper module that would contain code that you don’t want in your business logic. It is like a cleaning robot that makes sure that your house is always clean. The robot works independently and enhances the quality of your life; in the same way, an aspect enhances your code. In Spring, aspects are represented as classes annotated with @Aspect annotation.
Advice is the “what” of AOP; it is used to define what action should be performed at which point in the application. There are several types of advice in Spring and I first let you guess when it is usually executed:
I’m sure you managed to do it, but I still have to (just in case) add my clarifications on all the advice types:
Before: It is executed before a method.
After: It is executed after the method completion, irrespective of the method’s result.
AfterReturning: It is executed after the method has been completed successfully.
AfterThrowing: It is executed after the method has thrown an exception.
Around: It surrounds the method execution and can be the most versatile (e.g., timing how long a method takes to run).
@Aspect // This annotation defines a class as an Aspect, and will add this class in spring context
public class LoggingAspect {
@Before("execution(* dev.temnikov.service.*.*(..))") // Executes before any method in 'service' package
public void logBeforeMethodExecution() {
System.out.println("Logging BEFORE the method execution!");
}
}
Explanation:
A join point in your application is any point at which an aspect may be applied. Join points in a Spring include method calls, object initialization, and field assignments.
In Spring AOP, join points are all limited to method execution, so it’s pretty straightforward. Just to sum it up -> in Spring AOP you are not allowed to apply aspects on Object Initialization or field assignment and all your aspects should be associated with executions of methods.
Pointcut
A pointcut is a rule or expression that defines at which point advice should be applied. It is a filter that helps your code to select specific join points depending on different criteria. Such criteria might be method names, annotations, or parameters.
@Aspect
public class PerformanceAspect {
@Pointcut("execution(* dev.temnikov.service.*.*(..))") // Matches all methods in the 'service' package
public void serviceLayerMethods() {
// This method is just a marker; you should remain body empty
}
@Around("serviceLayerMethods()") // Applies advice to the defined pointcut
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // Executes the target method
long duration = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + duration + "ms");
return result;
}
}
Explanation:
joinPoint.proceed()
is the line of code you will use in more than 90% of your aspects because it executes the target method with all the parameters.
Spring relies on runtime weaving via proxies. For example, if you have a UserService class, Spring creates a proxy object that applies the configured aspects to method calls on that proxy object. Proxies are a critical part of AOP so let’s take a deeper look at proxies. So let’s dig into the inner workings of Spring AOP: dynamic proxies and dependency injection in the Spring container.
Spring AOP is built around proxies that sit between objects and intercept method calls to apply the aspects. There are 2 mechanisms used by Spring to create these proxies JDK Dynamic Proxies and CGLib. Let’s take a closer look at both of them:
The JDK Proxy class is used when the target object implements at least one interface. The proxy acts as an intermediary, intercepting calls to the interface methods and applying aspects.
How it works:
public interface UserService {
void performAction();
}
public class UserServiceImpl implements UserService {
@Override
public void performAction() {
System.out.println("Executing business logic...");
}
}
Let’s imagine we would like to create some Around aspect. If this aspect is applied to the UserService
interface, Spring will generate a proxy class like the code below. Pay attention to the way a proxy is created. It actually creates a new class using the Proxy class.
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
(proxyObj, method, args) -> {
System.out.println("Aspect: Before method call");
Object result = method.invoke(new UserServiceImpl(), args);
System.out.println("Aspect: After method call");
return result;
}
);
When the target class doesn’t implement any interfaces, then Spring uses CGLIB (Code Generation Library) to create a subclass proxy. This proxy simply overrides the target class methods and applies aspects around method calls.
Limitations:
Let’s create simple class called OrderService
with only one void method:
public class OrderService {
public void placeOrder() {
System.out.println("Placing an order...");
}
}
If we add some aspect using Spring AOP - Spring would generate a proxy overriding method.
public class OrderService$$EnhancerBySpring extends OrderService {
@Override
public void placeOrder() {
System.out.println("Aspect: Before placing the order");
super.placeOrder();
System.out.println("Aspect: After placing the order");
}
}
Note: For explanatory purposes I did not add any annotations in our base services: UserService and OrderService, my goal here was just to demonstrate the approach Spring will create a proxy IF we add an aspect
As we can see, using CGLib allows us to create a proxy by just extending the base class and overriding methods we need to proxy. That’s why the limitation we mentioned earlier arose: If our method we want to use as jointPoint or the class itself is marked as Final, we can not override it, or extend it, because of Java limitations.
AOP comes with its own set of challenges, despite AOP bringing numerous benefits such as separating concerns and reducing boilerplate code. In this chapter, we’ll cover situations where AOP may not be the ideal solution, along with some limitations of Spring AOP, and best practices to improve the readability and testability of your code with AOP.
Spring AOP only applies to Spring beans managed by the Spring container. For example, if a class is not a Spring bean (so it is not registered in the application context), then AOP won’t work. Moreover, Spring AOP doesn’t apply aspects to methods of beans called from within the same class since the proxy is created per bean.
To understand why it happens you need to realize the stack trace of method execution. So let’s consider following code
public class TestService {
@Transactional
public void A() { //Sorry for Upper Case naming
System.out.println("Hello from method A");
B()
}
@Transactional
public void B() {//Sorry for Upper Case naming
System.out.println("Hello from method B");
}
}
What will happen when somebody executes testService.A()
? First of all, method A() will be executed as a method of Spring bean, that’s why transactional annotation will be applied and a new transaction opens (or maybe not if you configured it another way, nevertheless @Transactional
will work as it should according to your configuration).
But will the Transactional aspect be applied to the second method execution? When method A() calls method B()? The answer is NO. Why? As I mentioned before we need to understand the stack trace of execution.
Method A as I mentioned a couple lines above is executed via Spring bean, so it is a proxied method. Actually when we execute testService.A()
our program executes testServiceProxy.A()
where testServiceProxy is a Spring generated proxy class with aspect applied.
But as we can see in the code method B() is executed from method A(). So actually in method A() we execute this.B(). We are calling method B not from proxy but from TestService itself, so there it is not extended with aspect code.
That’s why you should be careful when executing methods in one class. If you expect aspects to be implemented you should think about some workaround / code refactoring.
Tip:
@PostConstruct
and @PreDestroy
annotations for initializing and cleaning up beans in a way that AOP can manage.
If we think one way forward, we will realize why Spring AOP only intercepts method calls via public or protected methods and does not support aspects applied to private methods. To execute the proxied method we need to execute it from somewhere else: class from another package, or at least children class. So there is no sense in creating any proxies / aspects for private methods.
Tip:
Spring AOP is a powerful tool that introduces a lot of "magic" into your application. Every Spring developer uses this magic, sometimes without even realizing it. I hope this article has provided you with more clarity on how it works and how Spring creates and manages aspects internally.