Understanding AOP in Spring: from Magic to Proxies

Marcos Abel
Trabe
Published in
5 min readApr 27, 2020
Photo by Artem Maltsev on Unsplash

AOP is a great tool for keeping your codebase clean. It allows you to keep your code focused on the main task at hand and extract all the cutting concerns to manageable pieces of code.

I have taught some AOP lessons lately and have observed that most (junior) developers have a certain degree of fear of AOP that prevents them from taking advantage of it. The problem is, in my opinion, more a matter of poor knowledge of the internals, obscure syntax, and inconvenient APIs than a problem based on real complexity.

In this article, we are going to cover the basic mechanism that makes AOP possible and hopefully debunk the notion that AOP is magical and complicated.

Terminology

In this article, we will be using AOP-specific terminology. If you are not familiar with the terms advice or joinpoint, you can go here to get a notion of the basic concepts used when talking about AOP.

Understanding proxies

The most basic concept that we need to understand how AOP works in Spring is that of a Proxy. A proxy is an object that wraps another object maintaining its interface and optionally providing additional features. Proxies usually delegate behavior on the real object they are proxying but can execute code around the call to the wrapped object.

Spring uses proxies under the hood to support some of its magic features and AOP is not an exception.

Let’s see a proxy in action:

With this code in place, we can instantiate the account directly:

Running this code yields the following output:

Object Class: [ pojos.AccountImpl ]  
Account balance: [ 100.50 € ]

Now we can change our code to use a proxy:

When we run this new version, the output is:

Object Class: [ com.sun.proxy.$Proxy0 ] 
Account balance: [ 100.50 € ]

In this example, we use a ProxyFactory to programmatically create a proxy. In our day-to-day scenarios, the proxies will be transparently generated for us under the hood, but we want to create the proxy explicitly in this example to be fully aware of what’s going on here.

As you can see looking at the output generated when running the code, the runtime class of our Account is no longer AccountImpl. We have a proxy between our client code and our implementation.

When we call a method of our proxy, the call is delegated to the implementation class, but having these proxy objects allows for magic to happen. The proxy can just delegate to the implementing class or do things before, after, or around the delegation.

This is exactly what happens when we use AOP in Spring: our implementing object is proxified and our AOP code is executed by the proxy before, after, or around the invocation of the implementing object.

What happens when we call a proxy can be summarized with a simple figure:

It is important to notice that internal calls to AccountImpl will never fire AOP execution because the magic happens at the proxy level and, as a result, internal calls don’t get the opportunity to be advised. Let’s see this with an example:

In the real world we would rarely add our AOP code directly using the API, but for this example that is exactly what we are going to do. We are going to define a MethodBeforeAdvice and add it to our ProxyFactory :

This code creates a proxy with an Advice that gets called before the execution of every method of the proxified class.

When we execute this code, we get the following result:

Object Class: [ com.sun.proxy.$Proxy0 ]  
Calling method [ isSuspended ]
Is account suspended: [ true ]
Calling method [ getBalance ]
Unavailable balance.

What has just happened? The calls made through the proxy (account.isSuspended() and account.getBalance()) trigger the execution of the advice but the internal call to isSuspended() that takes place when we call account.getBalance() does not trigger the advice.

Let’s see how the objects interact in that call to account.getBalance():

In this example, we created everything from scratch, including the proxy. We purposely coded the example in that way in order to visualize the existence of the proxy. In our daily use of AOP in Spring we don’t create proxies and that’s the main reason why some people forget their existence.

Real-world example

In our projects, we rarely create AOP proxies programmatically. We usually manage AOP using @AspectJ. This style of coding allows us to declare Pointcuts and Aspects using annotations.

In order to use AOP, we first need to select the points of our code where we want an aspect to be executed. This task is performed by writing a Pointcut.

@Pointcut("execution(* adviseMe(..))")
private void anyMethodCalledAdviseMe() {}

Once we have a pointcut, we can use it in an Aspect. In this case, we are going to use @AspectJ annotations to define an Aspect that gets executed before calling a method called adviseMe :

Let’s define a service with an adviseMe method:

Now we need to configure our application. We need to enable @AspectJ using @EnableAspectJAutoProxy. The configuration for our example:

With this configuration in place, we can write our main class to launch the example:

In this piece of code, the presence of the proxy is less evident.

We create a context and register our configuration bean. Spring detects and registers all the components inside the package indicated in the @ComponentScan annotation present in SimpleAopExampleConfiguration.

Spring also detects all the advised components in our codebase and transparently generates proxies when needed. When we execute the code, we get the following output:

[ Main ] Service Class: [ $Proxy19 ]  
[ Simple Aspect]: Executing my advise!
[ Main ] Method response: [ Some result ]

Sounds familiar? As we can see we are consuming a proxy, which is the component that launches the AOP code we have set up using @AspectJ.

The limitations outlined in the first part of this article still apply: internal calls don’t get advised.

Wrapping up

Some people call it magic, but there are just proxies. For sure proxies used very smartly, but proxies after all.

It is important to know how things work under the hood. It’s the only way to fully understand both the possibilities and limitations of the technologies we use.

--

--