Sunday, April 7, 2013

Spring: implementing the Factory pattern

Although Spring, in itself, is already an example implementation of the Factory pattern (any Spring application's application context is just a giant Factory, right!), from time to time, we would like to implement this same pattern in our application logic just to keep our code clean and tidy. Let's see how we can do this, by a short example:

1 Defining an interface for the classes to be instantiated through the factory
Let's say we're working on a Spring application that handles document printing. For a given document, it is able to print it in either A4 or A5 format and either portrait and landscape layout. Each of these printing strategies extends a common interface :

package strategy;

import model.Document;

public interface IPrintStrategy {
 public void print(Document document);
}

Here are the concrete implementations for each printing strategy. To keep this example simple, each concrete printing strategy will only indicates what it is supposed to to:


package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A4Landscape")
public class PrintA4LandscapeStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A4 landscape document");
 }

}


package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A5Landscape")
public class PrintA5LandscapeStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A5 landscape document");
 }

}


package strategy;

package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A4Portrait")
public class PrintA4PortraitStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A4 portrait document");
 }

}



package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A5Portrait")
public class PrintA5PortraitStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A5 portrait document");
 }

}

For now, just note that each printing strategy interface is annotated as being a Spring component with a certain name. 

2 Defining the factory class interface
Now, we'll have to define an interface that is to be implemented by the factory class through which we will retrieve printing strategies:


package strategy;

public interface PrintStrategyFactory {
 
 IPrintStrategy getStrategy(String strategyName);
}

As you can see, the factory class will get a strategy name as input and will return an instance of IPrintStrategy.

3 Defining the Spring application configuration




 
 
  
  
 
 
 
 
 
 
 


In this configuration file:
  • we specify the package to scan for spring component detection (here, it's the "strategy" package)
  • we declaratively define a bean of type "ServiceLocatorFactoryBean" that will be the Factory.
  • we also define, and that's optional, name aliases, so that a same printing strategy could be retrieved either by the name specified in the @Component annotation, or by the alias.
4 Testing our example
With the following test class, we can check that everything works as expected:


import model.Document;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;

import strategy.PrintStrategyFactory;


@ContextConfiguration(locations = {"classpath:/spring-config.xml"})
public class SpringFactoryPatternTest extends AbstractTestNGSpringContextTests{
 
 @Autowired
 private PrintStrategyFactory printStrategyFactory;
 
 @Test
 public void printStrategyFactoryTest(){
  Document doc = new Document();
  
  printStrategyFactory.getStrategy("DEFAULT").print(doc);
  printStrategyFactory.getStrategy("A5L").print(doc);
  printStrategyFactory.getStrategy("A5P").print(doc);
  printStrategyFactory.getStrategy("A5Portrait").print(doc);
 }
}

For instance, when we ask the factory class to give us the printing strategy answering to the name "DEFAULT", we'll end up with an instance of  "PrintA4PortraitStrategy" as "DEFAULT" is an alias for the spring component with name "A4Portrait". 

From there, I believe you got how it works. If not, feel free to checkout the source code and execute the test class, and you'll grasp the whole concept in the blink of an eye.

5 Source code
The whole source code of this example is available here (github)

20 comments:

  1. Awesome Example. Made my day.

    ReplyDelete
  2. Is there a way to pass additional parameters such that you could define the method as

    IPrintStrategy getStrategy(String strategyName, Boolean watermark);

    and have the second parameter be a state variable for the strategy but still use the same strategy object defined by strategyName

    ReplyDelete
    Replies
    1. Nope. As the Spring documentation states it, an instance of ServiceLocatorFactoryBean
      would expect the provided interface (in my example, PrintStrategyFactory) to expose either a no-arg method, or a method with one argument.

      Delete
  3. If all the IPrintStrategy implementations have a constructor which takes a pojo as argument, how do i configure that pojo in spring?

    ReplyDelete
    Replies
    1. Hi there,

      In your situation, is your POJO also a Spring managed bean? If so, why don't you just have it autowired into the IPrintStrategy implementations? If not, the simplest solution would be adding a setter in the IPrintStrategy implementations and then, set the POJO after you have retrieved the bean instance.

      Delete
    2. Hello Yiu, Thanks for the wonderful example but quick question on the above question asked which is Constructor of Implementing class taking POJO as argument is this something Spring considering to implement in the next release..? Do you have any idea about this..?

      Delete
    3. Hi Reddy, thanks for your comment.

      I have no clue whether it is something that would be implemented in a coming Spring release. IMHO, it is not necessary as there are already a couple of workarounds. Also, I think it tends to break the whole dependency injection approach.

      Delete
  4. same result can be achieved by using method injection.

    ReplyDelete
  5. What about the scope of the bean. everything seems to be singelton here. How can i get request scope object.

    ReplyDelete
    Replies
    1. Annotating the concrete strategy classes (PrintA4LandscapeStrategy, PrintA4PortraitStrategy, ...) with @Scope(value=WebApplicationContext.SCOPE_REQUEST) should the trick.

      Based on my unit test class, if you annotate the strategies classes with @Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE), you'll get a new instance every time you ask the factory for a strategy object.

      Delete
  6. Thanks for good article.

    When i followed your instructions in my application i am getting below exception
    Could not autowire field: private com.xxx.xxx.xxx.ConverterFactory com.xxx.xxx.xxx.MyService.converterFactory; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xxx.xxx.xxx.ConverterFactory] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    This is happening when i autowired into Spring Service(@Service). Any idea.

    ReplyDelete
    Replies
    1. The error message says it all: there is no bean of type com.xxx.xxx.xxx.ConverterFactory registered in Spring's context. Are you sure this class has been properly declared as being a Spring bean?
      Could you post your code?

      Delete
    2. applicationContext.xml

      <context:component-scan base-package="com.xxx.xxx.converter"/>
      <bean class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean" id="converterFactory">
      <property name="serviceLocatorInterface" value="com.xxx.xxx.converter.ConverterFactory"/>
      </bean>
      <alias alias="custom" name="CustomConverter"/>


      // Factory Class returns Converter Interface
      package com.xxx.xxx.converter;

      public interface ConverterFactory {
      public Converter getConverter(String converterName);
      }




      package com.xxx.xxx.converter;

      public interface Converter {
      public List<Object> getMyObject(Source obj);
      }


      // Converter Interface implementation.
      package com.xxx.xxx.converter;

      import java.util.List;

      import org.springframework.stereotype.Component;

      @Component("CustomConverter")
      public class CustomConverter implements Converter{

      @Override
      public List<Object> getMyObject(MySubClass obj) {
      return null;
      }
      }

      Delete
    3. This comment has been removed by the author.

      Delete
    4. Hi there,

      Sorry for the belated reply. Based on the code excerpts you sent me, I don't see where's the problem. Would it be possible to send your work as a project archive I could import?

      Delete
    5. Hi Yiu,

      i found the issue with our code. Seems that we are using java code(by extending WebMvcConfigurerAdapter) instead of xml file and annotation. Looks like applicationContext.xml file is being ignored.

      Is there anyway can i create bean for ServiceLocatorFactoryBean and provide the serviceMappings in the java code ?

      Something like below,


      @Bean
      public ServiceLocatorFactoryBean createFactoryBean() {
      ServiceLocatorFactoryBean factory = new ServiceLocatorFactoryBean();
      factory.setServiceLocatorInterface(ConverterFactory.class);
      // Mappings goes here
      return factory;
      }

      Delete
    6. As you're using bean-based Spring configuration, you may go as follows:

      @Configuration
      public class AppConfig {

      @Bean(name = { "CustomConverter", "cc" }) // if several names, names other than the first are aliases
      public Converter createCustomConverterBean() {
      return new CustomConverter();
      }

      @Bean(name = { "ServiceFactory" })
      public ServiceLocatorFactoryBean createServiceLocatorFactoryBean() {
      ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
      bean.setServiceLocatorInterface(ConverterFactory.class);
      return bean;
      }
      }

      Then, when you get a reference on a ConverterFactory, calling getConverter("cc") returns an instance of CustomConverter.

      FYI, I tested this configuration by slightly adapting the initial source code from my example. If necessary, I could it to you.

      Cheers

      Delete
  7. ConverterFactory is same as PrintStrategyFactory in your example. So i dont think we need to declare this as Spring bean. Am i right?

    ReplyDelete