Search This Blog

Thursday 26 September 2013

Method Injection in Spring

I knew Spring supports two types of injection - setter and constructor. I had also heard of a third type called method injection. But having never come across a use case for the same, I pretty much ignored it. Until I gave an interview.
All was going well until this question came along:
"You have a singleton bean. Is there a way to inject a new bean X into this class whenever you call the getX property of this bean ?"
I was pretty flummoxed. Later on I hunted on the web and came across this post by Rod Johnson.
The below post is an adaption from the same to focus on the interviewer's question.
public class SampleTester {

   private Glove glove;

   public Glove getGlove() {
      return this.glove;
   }

   public void setGlove(final Glove glove) {
      this.glove = glove;
   }

   public boolean testSample(final String sample) {
      // 1: wear a glove
      // 2: test the sample
      // 3: Dispose glove
      // 4: return result
      return true;
   }
}
The SampleTester class is used to test samples. (I know the class seems highly convoluted - bear with me ) The testSample process follows a series of steps. For every sample tested a new pair of gloves is required. Normal code would be:
public boolean testSample(final String sample) {
      // 1: wear a glove
      Glove gloves = new Glove(); // or some factory method
      // 2: test the sample
      // 3: Dispose glove
      gloves = null;
      // 4: return result
      return true;
   }
However due to certain constraints it is not feasible to have the code for glove creation inside. Instead Glove has been defined as a bean which is injected into the class by the container. So the code would be:
public boolean testSample(final String sample) {
      // 1: wear a glove
      final Glove glove = this.getGlove();
      // 2: test the sample
      // 3: Dispose glove
      //??? 
      // 4: return result
      return true;
   }
This is where we are stuck. How do we dispose the glove safely ? The glove is injected into SampleTester at the time of creation. If we set it to null, than future invocations of the testSample method will fail.
What we need is that every time we make a call to getGloves we should get a new glove. No, the answer is not to treat glove property as a prototype. A new prototype bean is created everytime we need to inject the property as a reference in some bean. Here we need our method to be capable of injecting a new bean everytime it is called. One approach would be :
public class SampleTester implements ApplicationContextAware {

   private ApplicationContext applicationContextRef;

   public Glove getGlove() {
      // this is a bean with prototype scope
      return (Glove) this.applicationContextRef.getBean("prototypeGlove");
   }

   public boolean testSample(final String sample) {
      // 1: wear a glove
      Glove glove = this.getGlove();
      // 2: test the sample
      System.out.println("testing sample " + sample + " with Glove " + glove);
      // 4: Dispose glove
      glove = null;
      // 3: return result
      return true;
   }

   @Override
   public void setApplicationContext(final ApplicationContext applicationContext)
         throws BeansException {
      this.applicationContextRef = applicationContext;

   }

}
If I were to call the testSample method multiple times, each time the gloves displayed will be different. This code meets the requirement perfectly - with one drawback. Our application code is now tightly coupled with Spring. If we want to avoid that we use method injection.
Method Injection will allow Spring to inject a method into our class. A method that is capable of returning a new glove object everytime we need one.
public class SampleTester {

   public Glove getFreshGloves() {
      System.out.println("This should never be called "
            + "- overriden version will be used");
      return null;
   }

   public boolean testSample(final String sample) {
      // 1: wear a glove
      Glove gloves = this.getFreshGloves();
      // 2: test the sample
      System.out.println("testing sample " + sample + " with gloves " + gloves);
      // 4: Dispose glove
      gloves = null;
      // 3: return result
      return true;
   }

}
If we directly use this bean we will get a Null Pointer Exception. We need Spring to override our getFreshGloves method. That is specified in our xml:
<bean name="sampleTester" class="test.method_injection.SampleTester">
      <lookup-method name="getFreshGloves" bean="prototypeGloves" />
   </bean>

   <bean name="prototypeGloves" class="test.method_injection.Gloves"
      scope="prototype" />
Here we have specified where the Gloves object for getFreshGloves will come from. Spring Container will actually subclass our SampleTester class and override the method getFreshGloves. Within it, the container will get a bean by the name 'prototypeGloves' and return it. Thus a method has been injected by the container.
As it involves inheritance we obviously cannot define SampleTester as final or set the method testSample to final.
A best practice would be to obtain the method from an interface or inherit an abstract version from a parent class.
With respect to our question it is essential that the bean 'prototypeGloves' is a prototype scoped bean. If it is singleton than every call to getFreshGloves will return the same singleton instance.

No comments:

Post a Comment