Understanding the worker ants of Spring Aspect Oriented Programming: Proxies

Srishti Kohli
Walmart Global Tech Blog
5 min readNov 5, 2021

“If you want your code to be easy to write, make it easy to read.” — Robert C. Martin, Clean Code

Having a clear separation between main business logic and other cross-cutting concerns is imperative for clean and readable code. Concerns like Security and Transaction Management are not your core tasks at hand. Tangling these with the business code results in heavy coupling with core functionalities that might be affected if they fail. As a programmer, I firmly stand by this belief:

“Don’t walk away from dirty codebases…RUN!!”

Aspect-oriented Programming (AOP) is a great tool in your toolbox to cater to concerns that are not central to your core functionality. It allows you to add behaviour to the existing code without modifying the code per se. AOP complements OOP by providing another way to achieve modularity and reduce code clutter. Spring has its own AOP framework which is conceptually easy to understand and is an excellent solution to most problems in enterprise Java applications.

In this article, we are going to unlock the mysteries inside the black box of AOP to fully exploit its true power and highlight its limitations. If you’re not familiar with AOP literature, have a peek here.

Proxies and Run Time Weaving

A Proxy is an object created by the AOP framework in order to implement the aspect contracts. In simpler words, it is a wrapper around a bean instance that maintains the object’s interface and optionally adds additional behaviour. Spring uses proxies under the hood to auto-magically add additional behaviour without modifying existing code. This is achieved using either of two ways:

  1. JDK dynamic proxy —Spring AOP defaults to JDK dynamic proxies which enable any interface (or set of interfaces) to be proxied. Whenever the targeted object implements even one interface, then the JDK dynamic proxy will be used.
  2. CGLIB proxy — This is used by default if a business object does not implement any interface.

Since the proxy sits in between the caller of an object and the real object itself, it can do something before and after the target object is invoked, thus acting as worker ants of Spring AOP.

Spring AOP Process (Source)

The target object is effectively wrapped around by the proxy at runtime. When Spring determines that a bean is advised by one or more aspects, it generates a proxy for that bean to intercept method invocations and ensures that advice is run as needed. Targeted class instances are changed into proxies during application startup or any other time during runtime before it’s used. This is called Runtime Weaving. Any calls to targeted methods are intercepted accordingly by the target proxy classes to execute any suitable advice.

Proxy in Action

Consider this example, for the creation of Logger aspect, that logs time taken for every method annotated with @Loggable

Source Code

This yields the following output:

test method called
testUtil method called
Execution time for Test.test :: 18 ms
Out of Test
testUtil method called
Execution time for Test.testUtil :: 0 ms

When spring determines that the Test bean is advised by one or more aspects, it automatically generates a proxy for it to intercept all method invocations and to execute any associated advice when needed. However, it’s observable from the output that method execution time is logged for pojo.testUtil() call but not for this.testUtil() call in the test method. Why? That’s because the latter is not intercepted by the proxy but by the actual target class. As a result, the advice is never triggered. Let’s look at what actually happens to understand better.

Around advice UML call diagram

The pojo.test()call which is destined for the Pojobean is received by the proxy which then calls the advice method. The around advice then directly calls the target method. Once the call has finally reached the target object, any method calls that it may make on itself(here, this.testUtil()) are going to be invoked against the this reference, and not the proxy.

Thus, self invocation does not result in associated advice to run.

Note: The @Aspect annotation on a class marks it as an aspect and hence excludes it from auto-proxying. Consequently, it is not possible to have aspects themselves be the target of advice from other aspects in Spring AOP.

Performance Impact

Since a proxy is an added intermediate between the calling code and the target object, it’s not surprising that some overhead is introduced. It’s noteworthy that this overhead is fixed. The proxied invocation would add a fixed latency irrespective of the execution time of the plain method. The actual question is, should we care about this latency? Yes and No!

  1. If the additional behaviour itself has a far greater performance impact(like caching or transaction management) than the proxying mechanism itself, the overhead appears negligible. However, if the behaviour is fine-grained and needs to be applied to a large number of objects (like logging of every method), the overhead is no longer insignificant.
  2. Another point of concern is the number of proxied objects involved in one request. If a single request involves calls to hundreds or thousands of proxied methods, the overhead becomes significant and cannot be ignored.

For these rare scenarios where requirements cannot be solved using proxy-based systems, byte code weaving is preferred. Byte code weaving takes classes and aspects and produces woven .class files as output. As aspects are woven directly into code, this provides better performance but is harder to implement as compared to Spring AOP.

Summing Up

In order to use Spring AOP to its full potential, it is vitally important to grasp the fact that Spring AOP is proxy-based. Proxy wraps around an object and transparently adds additional behaviour to cater to multiple kinds of concerns. It has great benefits like enhanced code readability, simplified structure and centralised management. Proxies do have performance side effects which are, however, irrelevant in most circumstances.

References

--

--