paint-brush
Aspect Oriented Programming Matters More Than You Thinkby@daniltemnikov
New Story

Aspect Oriented Programming Matters More Than You Think

by Danil TemnikovJanuary 28th, 2025
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

AOP is useful in tackling cross cutting concerns which are fully incorporated in the development of an application but cannot be attributed to a specific class. AOP can help measure and log the execution time of all methods so that optimizations can be done quickly.
featured image - Aspect Oriented Programming Matters More Than You Think
Danil Temnikov HackerNoon profile picture

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.

What Is AOP and Why Does It Matter?

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.


A Peek Behind the Magic

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.

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 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 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:

  • @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. Proxies.

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:

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 by implementing 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 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;
        }
     );

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 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.

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 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.


  1. 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.

  2. 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:

  • 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 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:

  • If possible - consider refactoring private methods into public. Be careful not to break encapsulation.
  • 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.