Magic of Aspects: How AOP Works in Spring
Article explains how Aspect-Oriented Programming (AOP) simplifies modern app development by handling cross-cutting concerns like logging, security, and performance seam
Join the DZone community and get the full member experience.
Join For FreeIt is from modern applications that one expects a clean and maintainable codebase in order to be able to manage the growing complexity. This is where Aspect Oriented Programming (AOP) comes in. AOP is a paradigm that enables the developers to separate the cross-cutting concerns (such as logging, metrics, and security) from the business logic of the application, making the code both modular and easy to maintain.
Why Is It Important to Know AOP?
I’ll begin with a simple analogy: There are some things that you should do when building a house: you should think about the design of the house, about the rooms and the decor of the rooms.
There are some other things, however, that you should be able to do across all the rooms of the house: smoke detectors should be easy to install, and security cameras should be easy to wire. Wouldn’t it be inefficient to do each of these in every single room? AOP has an invisible team that applies these systems wherever they are required without having to modify your primary construction plan.
What Is AOP and Why Does It Matter?
Aspect Oriented Programming is a programming paradigm that is complementary to Object Oriented Programming (OOP). While OOP arranges code into objects and methods, AOP is concerned with aspects or parts of the program that are not dependent on the objects. AOP is useful in tackling cross-cutting concerns, which are needs that are fully incorporated in the development of an application but cannot be attributed to a specific class.
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 log a 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 and don’t want all users to be able to see each other's account balances and all other data? Don’t I have to put in role checking in every method? I can implement role-based access control and then forget about it 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 many 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. Therefore, These tasks are abstracted away from the developers by AOP so that they can focus on developing business logic without having to develop boilerplate code.
Without AOP, you would have to copy and paste millions of 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 just by adding a couple of annotations (I think annotations are the most convenient way, but they’re, of course, not the only option.)
A Peek Behind the Magic
I’ve just mentioned annotations, so let’s talk about them a little. In Spring, AOP often works hand-in-hand with annotations, making it almost magical.
For example:
- @Transactional annotation. Oh yeah, every single time you add this annotation, you actually add a new aspect to your code.
- @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.
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.
Key Terms
Aspect
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 that are annotated with @Aspect
annotation.
Advice
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:
- Before
- After
- AfterReturning
- AfterThrowing
- Around
I’m sure you've 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 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
- @Before indicates that this advice will run before the method execution.
- execution(* dev.temnikov.service.*.*(..)) is a pointcut expression defining where this advice applies. (We’ll discuss pointcuts soon!)
Join Point
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
- @Pointcut: Declares a reusable expression to match join points. You may think about pointcuts like filters.
- @Around: Wraps the method execution. As we mentioned in the Advice section, this advice executes your logic before and after target method execution.
- ProceedingJoinPoint: Represents the method being intercepted by Aspect. It allows us to control its execution.
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.
How AOP Works in Spring
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.
Proxies and Spring AOP
Spring AOP is built around proxies that sit between objects and intercept method calls to apply the aspects. Spring uses two mechanisms to create these proxies: JDK Dynamic Proxies and CGLib. Let’s take a closer look at both of them:
JDK Dynamic Proxies
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:
- A proxy class is generated at runtime by Spring.
- The proxy class implements the same interfaces as the target class.
- Method calls are made through the proxy, which applies aspects both before and after the execution of the target method.
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 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;
}
);
CGLIB Proxies
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:
- The target class cannot be final because then it will not be possible to extend it to create a proxy.
- As methods cannot be overridden, then they cannot be advised.
Let’s create a simple class called OrderService
with one void method:
public class OrderService {
public void placeOrder() {
System.out.println("Placing an order...");
}
}
If we add some aspect to it using Spring AOP — Spring would generate a proxy like this, just overriding methods.
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 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 the 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.
Spring AOP 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.
Methods on Beans
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 the 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");
}
}
So 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 of lines above, is executed via Spring Bean, so it is a proxied method. Actually, when we executetestService.A()
our program executestestServiceProxy.A()
where testServiceProxy is a Spring-generated proxy with aspect applied. - But as we can see in the code, method
B()
is executed from methodA()
. So, in methodA()
, we are actually executingthis.B()
. This means methodB()
is executed directly fromTestService
rather than through the proxy, so 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 or code refactoring.
Tip
- When you need internal method calls to be advised, use a proxy-targeted approach, like putting the internal method calls through Spring-managed beans (i.e., bail out the logic into other beans). Or, refactor the method into a separate service.
- You can also use
@PostConstruct
and@PreDestroy
annotations for initializing and cleaning up beans in a way that AOP can manage.
Private Methods and Constructors
If we think one way forward, we will be able to 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
- Consider refactoring private methods into public methods if possible (not always a good idea for encapsulation reasons).
- If you still need to add some aspect for constructors, use
@PostConstruct
. It will add some initialization logic, and as you can see in the name, it will be executed right after the constructor is initiated. - For advanced needs, you might have to switch to AspectJ, which provides compile-time or load-time weaving, and it may help you handle private methods and constructors, but this is out of the scope of this article.
Summary
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.
Opinions expressed by DZone contributors are their own.
Comments