Cleaner Spring MVC Controllers Using AOP and Filters
I use AOP for all kinds of stuff. I love having a clear separation between the main code and the cross-cutting concerns and, once you understand how AOP works in Spring and get used to the terminology, there is no coming back.
One scenario where I use AOP extensively is to advise controller methods. But I have a problem with what I consider to be the “classic way” of doing AOP.
Let me explain.
The classic way: get parameters from joinpoint
I call “the classic way” of doing AOP to an implementation with the following characteristics:
- The advised method includes in its signature all the parameters needed for the advice to perform its function.
- The advice gets whatever bits of data it needs from those parameters.
Let’s refactor the tracing functionality of a simple controller using AOP “the classic way”:
As you can see, we have a controller method that prints some tracing information and then performs its main task. The method receives a parameter of the type ContextInformation
which is resolved from a header using a HandlerMethodArgumentResolver
:
We can split the controller into a controller and an Aspect
. Let’s start defining a custom annotation:
Now we can define a @Before
aspect and write a pointcut to force its execution for every method annotated with our annotation:
We use an AopUtils
component for this project in order to keep our @Aspect
as clean as possible:
With all the AOP bits in place, we can rewrite our controller as follows:
We have now a cleaner controller, focused on its main task and we can handle the tracing functionality using a declarative approach.
The problem with this approach is that we need to keep the ContextInformation
parameter in our controller method’s signature.
This is really ugly and it is also dangerous: a member of the team can see an unused parameter and just remove it not being aware of the Aspect
working with that parameter.
A better way: attach the parameters to the request
In my previous story, I wrote about how attaching objects to the request using filters helped with the optimization of argument resolvers.
We are going to apply the same trick here:
We add a filter that stores the information we need in the request. The storage/retrieval is delegated to a RequestUtils
component:
We can now rewrite our TraceAccessDetailsAspect
:
And finally, get rid of the parameter in our controller method:
Wrapping up
We usually do things based on habits. But sometimes it pays off to think outside the box and combine different techniques to keep our code cleaner.