Improve Your Spring MVC Argument Resolvers Using Filters

Marcos Abel
Trabe
Published in
3 min readJan 11, 2021
Photo by Miryam León on Unsplash

Method argument resolvers are a great tool to keep your Spring application clean. Implementing the interface HandlerMethodArgumentResolver you can extract the logic needed to instantiate a custom-typed argument to a component and keep your controllers clean.

But in some situations, using method argument resolvers can lead to less-than-ideal performance.

What am I talking about

I’ll show you using an example:

  • Let’s say that we have an application that receives a custom header with a JSON representation of a context object.
  • We will resolve different arguments using that context object.
  • At some point, we will find some things that need to be executed more than once.

We can start the example code writing a resolver for the ContextInformation itself:

This component resolves ContextInformation from a header named X-App-Context. With this resolver properly registered (see my article on argument resolvers for details on registering the resolvers), we can write a controller to test the resolver:

We can test our controller and resolver using httpie:

Ok, everything looks good assuming that our ContextInformation is something like this:

Our argument is resolved properly and our controller gets to use the custom argument without polluting its code with the instantiation of the ContextInformation.

So far so good.

The problem

Now we want to add a new resolver for a new argument type. And we need the application context in order to get it:

At this point, we are doing almost the same task twice. Of course, we can refactor both resolvers to avoid repeated code, but the deserialization of the JSON contained in the header will be performed multiple times if both resolvers get executed. Let’s write a controller method to make this happen:

In this example, both resolvers are executed and the JSON is deserialized twice. It might not seem like a lot of extra work in this case. But the repeated processing could be much more costly in real-world scenarios.

Solving the problem with filters

We can use filters and request attributes to avoid this problem: we process the header in a filter and then, after deserializing the JSON, we store the deserialized object as a request attribute.

This technique is as old school as it gets, the support for request attributes is present in the servlet API since the first servlet spec.

Let’s write our filter:

In this case, we are using OncePerRequestFilter which is a Spring-aware filter implementation that fires just once per request. OncePerRequestFilter provides a template method doFilterInternalthat we can override to implement the behavior of the filter.

In this example, we ignore error handling and assume that the header is always present and well-formed.

With that assumption in mind, we know that we will have an attribute holding a properly instantiatedContextInformation for every request that reaches our server.

We can change our resolvers to take advantage of this fact:

The resolver for ContextInformation is now reduced to a single significative line of code. The same happens with the UserInformationtResolver:

A little bit of refactoring

The solution works properly now, but we can do a little better: both resolvers need to know about the name of the attribute, perform casts, etc.

We can write a utility component:

With this RequestUtils, our filter can now be implemented like this:

HeaderProcessorFilter.java

And our final resolvers look cleaner:

ContextInformationResolver.java
UserInformationResolver.java

I have uploaded the example code to my Github account to make it easier to follow and run the examples.

Wrapping up

Using good-old filters to set attributes we can make our job easier in different parts of our architecture. In this story, we talked about resolvers, but this trick can also be used to keep other parts of our system clean.

We will talk about a similar technique for AOP in the near future.

Stay tuned!

--

--