Tuesday, July 08, 2008

Spring method injection

I recently inherited some legacy code and it's been extremely difficult to refactor. It's a catch-22: you can't refactor without unit tests in place, but it's difficult to unit test since it wasn't written to be testable. [TDD can cure this. :)]

So after much hair-pulling and thought, I was able to extract a few interfaces and configure some of the business logic classes as Spring managed beans. The problem was that some of the dependencies were still being acquired via the "new" keyword, and it had to remain that way.

In other words, I had a singleton bean which required a non-singleton, or prototype. Not just once, at bean creation time, but a new instance every time. How could this be done, without mucking up my code with the Spring API? We definitely do not want a reference to a BeanFactory or ApplicationContext in our bean.

Enter one of the rarer forms of dependency injection: Method Injection (see section 3.3.8).

The recipe is:
  1. Create a method for the dependency and either make it protected abstract, or public.
  2. In your applicationContext.xml, instead of specifying <property name="someProperty">, you use
    <lookup-method name="theMethodWhichReturnsYourPrototype" bean="idOfPrototypeBean"/>

Let's look at an example:

We have a class Homer, which wants a new instance of another bean, a Donut, every time, instead of reusing the same Donut over and over again.

Now, for the applicationContext.xml:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="donut" class="demo.DonutImpl" scope="prototype">
<!-- inject dependencies here as required -->

<!-- homer (singleton) uses donut (prototype) -->
<bean id="homer" class="demo.Homer">
<lookup-method name="getDonut" bean="donut"/>

So what's going on here? To quote the Spring docs:
The Spring Framework implements this method injection by dynamically generating a subclass overriding the method, using bytecode generation via the CGLIB library.

So when Homer asks for a donut via getDonut(), the subclassed Homer, generated via CGLIB, will return a new instance of Donut, which has its dependencies injected by Spring.

Pros of this approach: we are decoupled from BeanFactory and the Spring API
Cons: making the bean abstract makes testing a little difficult, since you have to subclass the class, however, making it concrete and implementing the getter method, you avoid this.

No comments: