Search This Blog

Creating prototype Spring beans on demand using lookup-method

The strength of the Spring Framework is its emphasis on stateless services. Being totally against OOP, this approach has many pragmatic advantages, with low memory consumption, no cost of pooling and multithreaded safety at the top of the list. But sometimes you really need the context and having non-singleton beans with different state attached to each instance makes your code a lot cleaner and easier to read. Let’s start from stateless code and do some consecutive refactorings.

Every time a new flight is entered into the system, we validate it with multiple business rules using FlightValidator class (please forgive my complete absence of domain knowledge):

There is something really disturbing in this code. The context (Flight instance being validated) is passed over and over through subsequent method invocations. On the other hand, because the context exists on the stack (which is thread local in the contrary to the heap), the class is thread safe by its definition. But still the code is so awkward that having flight field accessible to every method in the class (and sacrificing thread safety) is very tempting*. The easiest solution is to have a new, separate instance of FlightValidator every time we need to validate a flight. So we create FlightValidator bean with prototype scope:

Now, every time there is a need for flight validation, we should ask Spring to create a new instance of FlightValidator (imagine the class has multiple dependencies on other beans, has some aspects woven, etc. – we can’t simply use new operator). The easiest way to do this is to implement BeanFactoryAware and use injected BeanFactory to fetch any bean from the client code:

Every time the code in line 10 is executed, new instance (prototype scope) of FlightValidator is created and returned. Maybe the goal is achieved, but the solution is pretty cumbersome. Not only we tie ourselves with the Spring API, but also we fetch bean by name (which is very verbose). Last but not least, prior to Spring 3.0 the getBean() always returned Object instance, forcing client code to downcast the result.

I used this pattern several times, always being disgusted. But then, by accident, I found lookup-method feature in Spring docs. BTW Spring documentation is a gift... and a curse (like detective Monk used to say) - so exhaustive and comprehensive that it’s hard to read it and get to know with everything. But back to Spring – the idea behind the lookup method is to drop BeanFactoryAware interface and simply create abstract no-arg method that returns bean of type we are willing to create (i.e. FlightValidator). Now the best part: we just tell Spring declaratively we want this abstract method to create FlightValidator instance every time it is called and Spring will implement this method at runtime (using CGLIB) for us! Just look how easy it is comparing to BeanFactory approach:

The 4th line of Spring XML is essential. We basically say: hey, createValidator method is abstract and every time it is called, return new instance (using lookup-method and BeanFactory with singleton beans doesn’t make any sense) of bean named flightValidator. And Spring is clever enough to harness CGLIB and dynamically implement createValidator() method instead of trying to instantiate abstract class. Pretty awesome!

This approach looks much nicer, does not involve the Spring API or force us to hardcode bean name in Java code. Now, when we have a fresh new instance of FlightValidator, we can perform some refactorings to take advantage of class instance variables:

First, we assign flight to a private field available for all validating methods. Now, every method can access this field and there’s no need for passing arguments all over. But sadly, it is just the beginning. One day we had to make all validating methods publicly accessible in order to call them separately. But now we need some way to initialize flight field and we certainly don’t want to go back to flight parameter in every method and flight field assignment at the beginning of each one. There is another accidental disadvantage of our solution: if particular instance of FlightValidator leaks to some other thread, this thread can call validate() method with other Flight causing race condition. If only we could make FlightValidator immutable by passing flight only once, binding FlightValidator permanently with this flight and being able to easily call every public validation method...

This is it! Our Holy Object Oriented Grail! Compiler forces us to pass valid flight instance (you may add validation to assert that) and after the FlightValidator is created, it will always reference the same flight. API is simple, class is thread safe, everybody is happy... Except our favorite framework... According to the documentation, lookup method discussed above mustn’t have any arguments. But suppose I don’t read the docs and simply add Flight parameter to lookup method and expect the magic to happen:

protected abstract FlightValidator createValidator(Flight flight);

I thought to myself that Spring will transparently pass lookup method parameter(s) to the FlightValidator constructor matching its declaration – and we have just created such a constructor. I run the application and it starts fine, but when I try to call lookup method I get:

Seems like Spring ignored lookup method with arguments and simply didn’t implement it using CGLIB. I would expect context startup to fail or at least warning that lookup won’t work (I even created SPR-7426) but sadly only unit tests can prevent you from such a mistake. And IntelliJ IDEA:

We might work around this limitation and create some sort of init(Flight flight) method instead of constructor. But what if we forget to call this method or call it twice? Thread safety, immutability and consistency are lost... Spring does not allow us to parameterize creation of prototype beans created by lookup method but come on, it’s open source and I am a programmer, I wouldn’t sleep at night if I at least didn’t try...

I quickly run my unit tests and now Spring generated code for my abstract lookup method (AbstractMethodError is gone) but still framework tries to instantiate FlightValidator bean using no-arg constructor, ignoring lookup method parameters. First success, another challenge.

Half hour later I finally make out how CGLIB works and how Spring uses it. I’ll skip CGLIB tutorial (maybe we’ll come back to this great library later), enough is to say that every time we call lookup abstract method, CGLIB synthesized class calls provided callback method, that for lookup method looks like this (excerpt from LookupOverrideMethodInterceptor inner class in org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy.CglibSubclassCreator):

Third line is crucial. We take bean name defined in Spring XML and fetch it from BeanFactory named owner. But hey, what is that, args array argument?!? And take a look at overloaded getBean(String name, Object... args) method, ready to be used! I’ll give it a try and see what happens:

Can you spot the difference? I run unit tests and can’t believe my own eyes – it works! It’s amazing, I only changed – not even added, changed! – two lines of code and unlocked this great feature. Now I can pass arbitrary set of parameters to the lookup method and they are going to be passed straight to the constructor of newly created object. Finally the lookup method idea makes sense – create new, fully customized and initialized object every time you request it. No need for further setup and danger of data inconsistency. Long live the Spring Framework!

If you like this feature, I opened SPR-7431 ticket, watch it and vote for it.

* one might argue that validate() should actually be a method of Flight and is an example of Feature Envy (see Martin Fowlers’ book) code smell. I already discussed how to get rid of this smell using Spring

I opened pull requests but it needs to be cleaned up, I got quite a few suggestions from Spring team. I will try to push the changes as fast as possible as I am also looking forward to this functionality, but I'm quite busy lately. Please be patient.