Announcement Announcement Module
Collapse
No announcement yet.
How to replace factories that take runtime arguments Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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
    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).

    Comment


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

      Regards,
      Andreas

      Comment


      • #4
        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

        Comment


        • #5
          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.

          Comment


          • #6
            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

            Comment


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

              Much appreciated.
              Stuart

              Comment

              Working...
              X