Results 1 to 7 of 7

Thread: How to replace factories that take runtime arguments

Hybrid View

  1. #1
    Join Date
    Jun 2006
    Posts
    3

    Question How to replace factories that take runtime arguments

    I have been using Spring for a few months now. The large application I'm working on has a number of factories which I can't see how to replace (or make work elegantly) with Spring.

    Consider this code snippet from one such factory:

    Code:
    public class SupplierServiceFactory
    {
      public ISupplierService create(int supplierId)
      {
        if (supplierId == 1)
        {
          return new FirstSupplierService();
        }
        else if (supplierId == 2)
        {
          return new SecondSupplierService();
        }
        else if (supplierId == 3)
        {
          return new ThirdSupplierService();
        }
      }
    }
    Sorry for the lame example, however you can see that I have a number of services each implementing the same interface. The factory dishes out the appropriate service based on a supplier id that is only determined at run-time.

    The only way I can see to handle this is as follows:

    XML Config file:
    Code:
    <beans>
      <bean id="suppSvc1" class="FirstSupplierService"/>
      <bean id="suppSvc2" class="SecondSupplierService"/>
      <bean id="suppSvc3" class="ThirdSupplierService"/>
    
      <bean id="suppFactory" class="SupplierServiceFactory">
        <property name="firstSupplierService" ref="suppSvc1"/>
        <property name="secondSupplierService" ref="suppSvc2"/>
        <property name="thirdSupplierService" ref="suppSvc3"/>
      </bean>
    </beans>
    Factory:
    Code:
    public class SupplierServiceFactory
    {
      ISupplierService firstSupplierService;
      ISupplierService secondSupplierService;
      ISupplierService thirdSupplierService;
      
      // Setter methods for Spring injection.
      public void setFirstSupplier(ISupplierService svc)
      {
        firstSupplierService = svc;
      }
      
      public void setSecondSupplier(ISupplierService svc)
      {
        secondSupplierService = svc;
      }
      
      public void setThirdSupplier(ISupplierService svc)
      {
        thirdSupplierService = svc;
      }
      
      // factory method  
      public ISupplierService create(int supplierId)
      {
        if (supplierId == 1)
        {
          return firstSupplierService;
        }
        else if (supplierId == 2)
        {
          return secondSupplierService;
        }
        else if (supplierId == 3)
        {
          return thirdSupplierService;
        }
      }
    }
    I hope I haven't made any typos, this is bogus code that I've just typed in.

    My question is, is there a better way of doing this? It is using injection so it's better than it was (I can create mock service objects and inject these into jUnit tests) but it still seems a bit clumsy to me.

    I could probably improve things by using a map to define each of the services used in the factory in the XML file, however this is not my major concern. It's more the factory create(int) method and the "if" statement within it.

    It also seems to me that this method would only be applicable if the services are singletons. Some of the factories I'm looking at deal with singleton services, but others deal with prototypes.

    Thanks,
    Stuart

  2. #2
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    This sounds like a good candidate for FactoryBean - you just add the params at runtime and the factorybean determine what object to return. Note that this fully configurable as you hide the internal map and allow the factoryBean to determine a new object on every request. However, you can serve the same objects (either from an internal cache or from Spring application context).
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  3. #3
    Join Date
    Aug 2004
    Posts
    2,715

    Default

    Maybe this could be useful for creation based on runtime properties.

    Regards,
    Andreas

  4. #4
    Join Date
    Jun 2006
    Posts
    3

    Default

    Thanks a lot for the replies. I'd prefer to stick with a vanilla Spring solution if I can Andreas.

    Costin - can you expand on your FactoryBean suggestion? Do you have a particular implementation of this interface in mind (there are plenty to choose from).

    I'm unsure if you're suggesting a programmatic solution. Can you point to or provide any examples of how you'd go about it?

    Thanks again,
    Stuart

  5. #5
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    I am suggesting a programmatic solution; something like this:
    Code:
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.BeanFactoryAware;
    import org.springframework.beans.factory.FactoryBean;
    
    public class SomeFB implements FactoryBean, BeanFactoryAware {
    
        private BeanFactory beanFactory;
        
        public Object getObject() throws Exception {
            // determine the int id somehow
            int id = ...
            return beanFactory.getBean(ROOT_BEAN+id);
        }
    
        public Class getObjectType() {
            return ISupplierService.class;
        }
    
        public boolean isSingleton() {
            return false;
        }
    
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }
    }
    In this example I just used the factory to redirect to one of the existing configuration inside Spring - however, you can also include the creation of the beans (if it's possible or easier) inside the bean factory instead of doing the lookup inside the beanFactory.
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  6. #6
    Join Date
    Oct 2004
    Location
    Fareham, England
    Posts
    313

    Default

    Hi Stuart

    The class referred to below might be what you are looking for...

    http://static.springframework.org/sp...api/index.html

    The Javadocs are (to my mind) fairly comprehensive; to tailor them to fit your specific use case, the following configuration would effect what you want.

    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans
            xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:aop="http://www.springframework.org/schema/aop"
            xmlns:tx="http://www.springframework.org/schema/tx"
            xmlns:lang="http://www.springframework.org/schema/lang"
            xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
    
        <bean name="1" class="forums.StubSupplierService">
            <property name="name" value="First"/>
        </bean>
    
        <bean name="2" class="forums.StubSupplierService">
            <property name="name" value="Second"/>
        </bean>
    
        <bean name="3" class="forums.StubSupplierService">
            <property name="name" value="Third"/>
        </bean>
        
        <bean id="accountingService" class="forums.DefaultAccountingService">
            <property name="supplierServiceFactory">
                <bean class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
                    <property name="serviceLocatorInterface" value="forums.ISupplierServiceFactory"/>
                </bean>
            </property>
        </bean>
    
    </beans>
    With classes and interfaces like this...

    This is the main 'service locator / factory' referred to in your posts; I even kept the same name

    Code:
    package forums;
    
    public interface ISupplierServiceFactory {
        
        ISupplierService create(int supplierId);
    }
    And this is the thing that it makes; I also kept the same name and bunged in some throwaway method.

    Code:
    package forums;
    
    public interface ISupplierService {
        
        void execute();
    }
    A grungy little stub implementation of the above interface.

    Code:
    package forums;
    
    public class StubSupplierService implements ISupplierService {
    
        private String name;
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void execute() {
            System.out.println(name);
        }
    }
    This is the class into which you would inject an instance of the ISupplierServiceFactory (Spring will supply an implementation of the ISupplierServiceFactory) .

    Code:
    package forums;
    
    public class DefaultAccountingService implements AccountingService {
        
        private ISupplierServiceFactory supplierServiceFactory;
    
    
        public void setSupplierServiceFactory(ISupplierServiceFactory supplierServiceFactory) {
            this.supplierServiceFactory = supplierServiceFactory;
        }
    
    
        public void someBusinessLogic(Long serviceId) {
            ISupplierService supplierService = this.supplierServiceFactory.create(serviceId.intValue());
            supplierService.execute();
        }
    
    }
    And here's some driver code to exercise the above code and configuration.

    Code:
    import forums.AccountingService;
    import forums.ISupplierService;
    import org.springframework.context.support.AbstractApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public final class Boot {
    
        public static void main(final String[] args) throws Exception {
            AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
                    new String []{"beans.xml"}, ISupplierService.class);
            ctx.registerShutdownHook();
            AccountingService service = (AccountingService) ctx.getBean("accountingService");
            service.someBusinessLogic(new Long(1));
        }
    }
    The resulting output from the execution of the above program would be the glorious String 'First'. Hardly thrilling, but I guess (hope) it demonstrates runtime argument factory usage within Spring (via interfaces).

    The benefits of this approach are that you are only tasked with writing an interface; Spring's IoC container supplies the implementation (nothing prevents you from writing your own implementation, so you are not locked in architecturally). You are not limited to using just ints and stuff as the bean name / factory argument... the toString() mehtod of the argument will be used to get a bean name. Unfortunately this is not configurable at the moment (i.e. there is no strategy interface so that one can plug in one's own implementation), but if you have a look at the source code for the SLFB class you'll see that it would be fairly easy to either a) roll your own custom implementation along the same lines, or b) (better) create a JIRA issue asking for any customisation to be rolled into the Spring core.

    Hopefully this is helpful

    Merry Christmas
    Rick

  7. #7
    Join Date
    Jun 2006
    Posts
    3

    Default

    Thank you all for your help and examples. The ServiceLocatorFactoryBean looks like it will fit my requirements.

    Much appreciated.
    Stuart

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •