13

I am using Spring DI to wire my components and I came across this issue.

I have a BaseService class which has multiple implementations. And the layer above it, has a builder which calls the service to get data to populate POJOs. Service implementation I need to call (ServiceA,ServiceB) changes according to the type of POJO I need to build.

In such case, how can I autowire the service, as it requires late binding the service. How can I tackle this kind of scenario? (Example in Spring DI would really help)

Builder calling Services

I read similar questions but could not find the answer. And I read that SOA patterns such as Service Host provide different solutions to exact use case.

Please help. Thanks

  • In spring it is one time binding at startup. Do you want different service impl to be called depending on the pojo type you are passing? – bitkot Jun 01 '14 at 04:49
  • That is exactly what I want. – Chinthaka Dharmasiri Jun 01 '14 at 04:57
  • You could use Spring AOP (with an `Around` advice) to intercept the call to `Builder.buildPOJO(MyPojo.class)` method and inside the advice, depending on the class that is being passed as a parameter, the advice would call `ServiceA.buildPOJO()` or `ServiceB.buildPOJO()` etc. – Andrei Stefan Jun 01 '14 at 07:59

5 Answers5

7

How about using a FactoryBean:

public class BuilderFactory implements FactoryBean<Builder> {

  @Autowired
  private ApplicationContext appContext;

  ...

  @Override
  public Builder getObject() {
      Builder builder = new Builder();      
      switch(something()) { 
         case "foo":
             builder.service = new ServiceA(); 
             break;
         case "bar":
             builder.service= new ServiceB();
             break;
         ...
         default:
             //handle cases where it's unclear which type to create

         }
     return builder;
  }

}

where Builder instances have a public/package-private field BaseService service that gets called in their getData(), buildPojos() and wherever other methods.

(you could also use static factory methods to instantiate Builder if you want this field to be private)

drew moore
  • 31,565
  • 17
  • 75
  • 112
  • 2
    I forgot about FactoryBeans. – Jasper Blues Jun 01 '14 at 04:57
  • What would be the advantage of this pattern? What I mean is, we are using an additional switch case here, similar to what would be in my builder class implementation. Cant make the Builder to be singleton in this case also right? so would be this be an effective design strategy? – Chinthaka Dharmasiri Jun 02 '14 at 16:35
5

You can use ServiceLocatorFactoryBean. In your case you would do something like this:

public interface BaseServiceLocator {

   BaseService lookup(String qualifier); //use whatever qualifier type makes sense here
}

<bean id="serviceLocatorFactoryBean"
    class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    <property name="serviceLocatorInterface"
              value="your.package.BaseServiceLocator" />
</bean>

Then your builder would look something like this:

public class Builder {

  @Autowired
  private BaseServiceLocator baseServiceLocator;

  @Override
  public YourReturnType businessMethod() {
      SomeData data = getData();
      BaseService baseService = baseServiceLocator(data.getType()); //here I am assuming that getType() is a String

      //whatever
  }
geoand
  • 60,071
  • 24
  • 172
  • 190
2

I had the same requirement in one of my projects. I used reflection to get the services according to the pojo requirement. This way there will be no static values even if you define new pojo and service in future you wont have to change any implementation.

I had named my pojos and Services similarly. ie

POJO Name:Pond5DownloadStrategy and ServiceName: Pond5DownloadStrategyService.

I defined all the services in spring. I had a DownloadStrategyFactory which had a single method getService(Object obj). which is also instantiated as spring bean. what getService method did is. I get the POJO name as string using obj.getClass().getSimpleName() and then I append Service at the end. ex. If I pass Pond5DownloadStrategy then I do AppContext.getBean("Pond5DownloadStrategyService");

bitkot
  • 4,466
  • 2
  • 28
  • 39
2

Please look at my answer here.

Although is under spring batch topic it’s actually related to your question and the Strategy Design pattern.

StrategyA StrategyB are your ServiceA,ServiceB etc.
You need to use the StrategyLocator in your Builder class (in the original answer it’s equivalent is MyTaskelt). The look-up will be based on your pojo type.

strategy = strategyLocator.lookup(POJOs.class);  

In the answer I suggested a PlugableStrategyMapper, but if you predefine all Servcies you can place them in a Map in the application-context.xml

Community
  • 1
  • 1
Haim Raman
  • 11,508
  • 6
  • 44
  • 70
1

For example, for manual binding:

public class Builder {

    @Autowired
    private Map<String, Service> services;
    // Bind pojo classes to bean names.
    private Map<Class<?>, String> binding;

    public Service getService(Object object) {
        return services.get(binding.get(object.getClass()));
    }

    public Map<Class<?>, String> getBinding() {
        return binding;
    }

    public void setBinding(Map<Class<?>, String> binding) {
        this.binding = binding;
    }
}

However, manual binding could be repetitive so if you don't really need his flexibility, you could use a naming convention (@AmitChotaliya answer) or enforce the binding via Service method.

public interface Service {

    Class<?> getTargetType();
}


public class Builder {

    @Autowired
    private Set<Service> services;
    // Bind pojo classes to Services.
    private Map<Class<?>, Service> binding = new ConcurrentHashMap<Class<?>, Service>();

    @PostConstruct
    public void init() {
        for (Service service : services) {
            binding.put(service.getTargetType(), service);

        }
    }

    public Service getService(Object object) {
        return binding.get(object.getClass());
    }
}
Jose Luis Martin
  • 10,459
  • 1
  • 37
  • 38