Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Autowire candidate beans in Spring 3

Say I have the following structure with a service interface ServiceInterface and a couple of components implementing it: ProductAService and ProductBService I also have a RequestContext bean that has a qualifying property that says that we're say currently processing ProductA or ProductB. How can then automatically inject with autowiring or other annotation the correct implementation (ProductAService or ProductBService) into some service that needs it (ServiceThatNeedsServiceInterface below).

public interface ServiceInterface {
  void someMethod();
}

@Component(name="ProductAService")
public class ProductAService implements ServiceInterface {
  @Override public void someMethod() { 
    System.out.println("Hello, A Service"); 
  }
}

@Component(name="ProductBService")
public class ProductBService implements ServiceInterface {
  @Override public void someMethod() { 
    System.out.println("Hello, B Service"); 
  }
}

@Component
public class ServiceThatNeedsServiceInterface {

  // What to do here???
  @Autowired
  ServiceInterface service;

  public void useService() {
    service.someMethod();
  }
}

@Component
@Scope( value = WebApplicationContext.SCOPE_REQUEST )
public class RequestContext {
  String getSomeQualifierProperty();
}
like image 795
Strelok Avatar asked Feb 21 '13 00:02

Strelok


1 Answers

Spring Source were referring to your issue when they created the ServiceLocatorFactoryBean back in version 1.1.4. In order to use it you need to add an interface similar to the one below:

public interface ServiceLocator {
    //ServiceInterface service name is the one 
      //set by @Component
    public ServiceInterface lookup(String serviceName);
}

You need to add the following snippet to your applicationContext.xml

<bean id="serviceLocatorFactoryBean"
    class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    <property name="serviceLocatorInterface"
              value="org.haim.springframwork.stackoverflow.ServiceLocator" />
</bean>

Now your ServiceThatNeedsServiceInterface will look similar to the one below:

@Component
public class ServiceThatNeedsServiceInterface {
    // What to do here???
    //  @Autowired
    //  ServiceInterface service;

    /*
     * ServiceLocator lookup returns the desired implementation
     * (ProductAService or ProductBService) 
     */ 
 @Autowired
     private ServiceLocator serviceLocatorFactoryBean;

     //Let’s assume we got this from the web request 
     public RequestContext context;

     public void useService() {
        ServiceInterface service =  
        serviceLocatorFactoryBean.lookup(context.getQualifier());
        service.someMethod();         
      }
}

ServiceLocatorFactoryBean will return the desired service based on the RequestContext qualifier. Apart from spring annotations your code is not depended on Spring. I executed the following unit test for the above

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:META-INF/spring/applicationContext.xml" })
public class ServiceThatNeedsServiceInterfaceTest {

    @Autowired
    ServiceThatNeedsServiceInterface serviceThatNeedsServiceInterface;

    @Test
    public void testUseService() {
    //As we are not running from a web container
    //so we set the context directly to the service 
        RequestContext context = new RequestContext();
        context.setQualifier("ProductAService");
        serviceThatNeedsServiceInterface.context = context;
        serviceThatNeedsServiceInterface.useService();

        context.setQualifier("ProductBService");
        serviceThatNeedsServiceInterface.context = context;
        serviceThatNeedsServiceInterface.useService();
    }

}

The console will display
Hello, A Service
Hello, B Service

A word of warning. The API documentation states that
“Such service locators … will typically be used for prototype beans, i.e. for factory methods that are supposed to return a new instance for each call… For singleton beans, direct setter or constructor injection of the target bean is preferable.”

I cannot understand why this may cause an issue. In my code it returns the same service on two sequence calls to serviceThatNeedsServiceInterface.useService();

You can find the source code for my example in GitHub

like image 61
Haim Raman Avatar answered Oct 13 '22 02:10

Haim Raman