Question

I'm trying to grasp Spring's FactoryBean and I've had and issue. Could you please see my sources below and answer. It's my app context:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:annotation-config/>

    <bean id="SHADigest" class="com.dtoryanik.spring.factorybean.MessageDigestFactoryBean">
        <property name="algorithmName">
            <value>SHA1</value>
        </property>
    </bean>

    <bean id="defaultDigest" class="com.dtoryanik.spring.factorybean.MessageDigestFactoryBean"/>

    <bean id="digester" class="com.dtoryanik.spring.factorybean.MessageDigester">
        <property name="messageDigest1">
            <ref local="SHADigest"/>
        </property>
        <property name="messageDigest2">
            <ref local="defaultDigest"/>
        </property>
    </bean>
</beans>

It's a factory bean actually:

public class MessageDigestFactoryBean implements FactoryBean<MessageDigest>{

    private String algorithmName = "MD5";
    private MessageDigest messageDigest = null;

    @Override
    public MessageDigest getObject() throws Exception {
        System.out.println("<> MessageDigestFactoryBean.getObject()");
        return messageDigest;
    }

    @Override
    public Class<?> getObjectType() {
        System.out.println("<> MessageDigestFactoryBean.getObjectType()");
        return MessageDigest.class;
    }

    @Override
    public boolean isSingleton() {
        System.out.println("<> MessageDigestFactoryBean.isSingleton()");
        return true;
    }

    @PostConstruct
    public void postConstructHandler() throws NoSuchAlgorithmException {
        System.out.println("<> MessageDigestFactoryBean.postConstructHandler()");
        messageDigest = MessageDigest.getInstance(algorithmName);
    }

    public void setAlgorithmName(String algorithmName) {
        this.algorithmName = algorithmName;
    }
}

There is another class - MessageDigester but it does not do anything helpful for topic. And I have a main-method class:

public class MessageDigestDemo {

public static void main(String[] args) {
    GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
    ctx.load("classpath:app-ctx.xml");
    ctx.refresh();

    MessageDigester messageDigester = (MessageDigester) ctx.getBean("digester");
    messageDigester.digest("Hello World!");
}

}

The issue is in my output. It seems I have a double instantiating. Methods isSingleton(), getObject() are called two times for each bean (although I retrieve only 2 instances from factory). Why does it occur? Maybe I do something wrong?

<> MessageDigestFactoryBean.postConstructHandler()
<> MessageDigestFactoryBean.postConstructHandler()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObject()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObject()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObjectType()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObjectType()
Was it helpful?

Solution

I've modified your MessageDigestFactoryBean class so that it outputs algorithmName as well, this will help to clear out the case. After that change, the output is:

<> MessageDigestFactoryBean.postConstructHandler() SHA1
<> MessageDigestFactoryBean.postConstructHandler() MD5
<> MessageDigestFactoryBean.isSingleton() SHA1
<> MessageDigestFactoryBean.getObject() SHA1
<> MessageDigestFactoryBean.isSingleton() MD5
<> MessageDigestFactoryBean.getObject() MD5
<> MessageDigestFactoryBean.isSingleton() SHA1
<> MessageDigestFactoryBean.getObjectType() SHA1
<> MessageDigestFactoryBean.isSingleton() MD5
<> MessageDigestFactoryBean.getObjectType() MD5

Let's try to analyze this output.

  1. You have declared two MessageDigestFactoryBean instances, so when Spring discovers them in context, it initializes them, and in process calls the method annotated with @PostConstruct - MessageDigestFactoryBean.postConstructHandler().
  2. Then as Spring discovers digester bean, it tries to obtain it's dependencies. Spring sees that dependency is FactoryBean, so it eventually calls FactoryBeanRegistrySupport.getObjectFromFactoryBean. This method first checks if the bean is singleton, calling MessageDigestFactoryBean.isSingleton(). If the bean is singleton, it first tries to obtain reference to it from factory bean objects cache. Since this is the first time this bean is referenced, it's not yet cached, so reference is obtained via MessageDigestFactoryBean.getObject(), cached and then returned. Since you have two factory beans referenced in digester, obviously this process is repeated for each one.
  3. After initialization is complete, Spring tries to publish ContextRefreshedEvent to the lifecycle processor, so it searches all bean definitions for an instance that implements Lifecycle interface. Basically Spring loops through all beans defined in context, checking for singleton bean of matching type (actually there are more checks, but we're interested only in these). In process of this, for factory bean MessageDigestFactoryBean.isSingleton() is called to determine whether object is singleton, and MessageDigestFactoryBean.getObjectType() is called to check whether object's type is assignable from Lifecycle interface. Again, since you have two instances of MessageDigestFactoryBean, each of these methods is called twice.

That's what happens when you call ctx.refresh(). This is just over the top look and obviously a lot more is done by Spring under the hood, but that's all I can see related to your output. Hope this answers your first question.

Now for the second question - no, you did not do anything wrong, the output you see just reflects how Spring functions internally.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top