Sunday, April 28, 2013

Spring beans with same name and @Configuration

One of the important features when testing an application is being able to replace some of the real services with test doubles. With a Spring based application, this has typically been done by defining the test doubles with the same name as the original bean which is being mocked out and this works as the bean defined later is the one which is finally instantiated by the Spring container.

Consider a bean, call it memberService bean, having two implementations one defined using XML based bean definition files say in a context1.xml file:

<bean name="memberService" class="samples.config.MemberSvcImpl1"/>

and if a different implementation of memberService is needed for test, the way to do it would have been to define the bean with the same name, this way(say in a context2.xml file):

<import resource="context1.xml"/>
<bean name="memberService" class="samples.config.MemberSvcImpl2"/>

since the MemberSvcImpl2 is defined after MemberSvcImpl1, the MemberSvcImpl2 would be available for injection in tests.


However with @Configuration style of defining beans, things are not so clear cut. What does it mean to define a bean after another bean, consider the following which tries to mimic the definition in context2.xml file above:

@Configuration
@ImportResource("classpath:samples/config/context1.xml")
public class Context2Config {
 @Bean
 public MemberService memberService() {
  return new MemberSvcImpl2();
 }
}

Here the memberService defined in the @Configuration DOES NOT override the memberService bean defined in the xml. Something to be careful about.

The solution is actually simple - if you need to override a previously defined bean(without say the flexibility of autowiring with a different bean name), either use the XML bean configuration for both the bean being overridden and the overriding bean or use the @Configuration. XML bean configuration is the first example in this entry, the one with @Configuration would be something like this:

@Configuration
public class Context1JavaConfig {
 @Bean
 public MemberService memberService() {
  return new MemberSvcImpl1();
 }
}

@Configuration
@Import(Context1JavaConfig.class)
public class Context2JavaConfig {
 @Bean
 public MemberService memberService() {
  return new MemberSvcImpl2();
 }
}

This would work as expected. I am sure there are alternate ways of getting this to work, but the approach that I have outlined here has worked well for me and I have been sticking to this pattern