Wednesday, 8 June 2011

Implementing Spring's FactoryBean Interface

There are times, and not many of them, when Spring cannot load an object in to its context. This is usually the case then the object you’re trying to build has a private constructor and a static factory method (which by convention should be called getInstance()) and cannot be instantiated by simply calling new. When this happens there are a couple of approaches you can take. I’ve already mentioned using the new Java based dependency injection with its @Configuration and @Bean annotations; however, another approach is to write a factory class that implements the FactoryBean interface.

This is a simple interface that has three self explanatory methods:
  • getObject()
  • getObjectType()
  • isSingleton()

...all of which are fully explained in the Spring documentation.

Note that your program should never need to access the FactoryBean implementation directly.

To demonstrate this, consider the Java API’s java.security.MessageDigest: this has a getInstance() static factory method. If we want to make this class available to Spring, we wrap it in a factory and load a java.security.MessageDigest into an instance variable and return that variable when asked to by Spring.

public class MyFactoryBean implements FactoryBean<MessageDigest> {

 
private static String MESSAGE_DIGEST_ALGORITHM = "MD5";

 
private MessageDigest messageDigest = null;

 
public MyFactoryBean() throws NoSuchAlgorithmException {

   
messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM);
 
}

 
public MessageDigest getObject() throws Exception {

   
return messageDigest;
 
}

 
public Class<MessageDigest> getObjectType() {

   
return MessageDigest.class;
 
}

 
public boolean isSingleton() {

   
return true;
 
}
}

Having written our factory, the next step is to write a simple POJO that acts as a client which uses the java.security.MessageDigest class. There is nothing special about this class; it has a constructor that takes a MessageDigest class and it uses this class together with a Base64 class to encode a string using the MD5 algorithm.

public class MyDigesterBean implements MyDigesterBeanItf {

 
private final MessageDigest messageDigest;

 
public MyDigesterBean(MessageDigest messageDigest) {

   
this.messageDigest = messageDigest;
 
}

 
/**
   * A simple business method. Takes an input string and converts it to BASE64 for output.
   */
 
public String encodeString(String arg) {

   
byte[] out = messageDigest.digest(arg.getBytes()); // convert to encoded bytes

   
Base64 enc = new Base64(); // convert to readable bytes
   
return enc.encodeToString(out);
 
}
}

public interface MyDigesterBeanItf {

 
public String encodeString(String inMsg);
}

The next step is to configure the XML and when you look at the config below, you’ll see that what’s happening isn’t at all obvious.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 <!-- Create a reference to the Factory Bean -->
 <bean id="MyFactoryBean" class="example_3_spring_aware.creating_factory_beans.MyFactoryBean"/>
 <!-- This bean uses the Factory bean to set the digest property --> 
 <bean id="digestBean" class="example_3_spring_aware.creating_factory_beans.MyDigesterBean">
  <constructor-arg>
   <ref local="MyFactoryBean"/>  
  </constructor-arg>
 </bean>
</beans>

According to the XML, the MyDigesterBean has a constructor arg that takes a class of type MyFactoryBean, BUT, we know from the code that that’s not true. I’ll hazard a guess that what’s actually happening is that Spring checks the constructor arg type, figures out that it’s a FactoryBean and then uses the values returned by FactoryBean to inject into MyDigesterBean.

To run this sample, you’ll need a lump of test code:

    // Create a FileSystemResource - a class based upon AbstractResource
   
Resource resource = new FileSystemResource(Constants.PATH_TO_RESOURCES
        +
"example3_FactoryBean.xml");

   
// Using the resource - load the BeanFactory
   
BeanFactory factory = new XmlBeanFactory(resource);

   
// Get hold of the MyDigesterBean interface - this is created using the MessageDigest
    // class created by our factory bean: MyFactoryBean.
   
MyDigesterBeanItf bean = factory.getBean(MyDigesterBeanItf.class);

   
// Use the bean to convert this string
   
String str = bean.encodeString("Hello World");

    System.out.println
("Converted String Hello World to: " + str);

This example requires three JAR files on the classpath:
  • spring-core.jar - for the FileSystemResource class
  • sping-beans.jar - for the XmlBeanFactory class
  • commons-logging.jar (from Jakarta Commons project) - used by the other two libraries

3 comments:

Anonymous said...

This did exactly what I needed. Thank you for posting such a clear example.

sravan kumar said...

We cannot get instance of Factorybean, whenever we inject or autowire a bean of FactoryBean type spring will get the underlying instance.
In you configuration, although u said the bean id as MyFactoyrBean bu in reality it is instance of MessageDigest

Roger Hughes said...

Sravan
What you are seeing is correct behaviour, Spring will always hide the FactoryBean from you when you try to inject one into one of your other Spring Beans.