Monday, 1 March 2010

Spring: setting static fields

This is pretty obscure, but I just found this nice trick with Spring framework, for injection of values into static fields: I've been looking for a way to do this for AGES.

What that article doesn't mention, and the key benefit in my project, is that when you use @AutoWired, you can have the Spring-invoked initialization method marked as private, to avoid polluting your public API.

Spring config:
<beans xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <!-- Scan for @Autowired annotations -->
    <context:annotation-config>

    <!-- The instance to be injected into the static field on StaticHub -->
    <bean class="internal.stuff.MyInterfaceImpl" name="myPrecious">

    <!-- The class which will have its static field set via @Autowired -->
    <bean class="very.public.api.StaticHub" name="dummyInstanceOfStaticHub">
</beans>

Java class with static field:
package very.public.api;

import org.springframework.beans.factory.annotation.Autowired;

public final class StaticHub {
    private static MyInterface theStaticInstance;

    /**
     * Note this initialization method is private! No nasty public setInstance method.
     */
    @Autowired(required = true)
    private StaticHub(MyInterface instance) {
        theStaticInstance = instance;
    }

    /**
     * My public API, making the Spring-created instance of MyInterface statically accessible
     */
    public static MyInterface getInstance() {
        return theStaticInstance;
    }
}

This is a big improvement over my previous unsatisfactory solution, which was to use org.springframework.beans.factory.config.MethodInvokingFactoryBean with a public method on StaticHub like this:
<bean name="staticHubInitializer" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="very.public.api.StaticHub.setInstance"/>
        <property name="arguments">
            <list>
                <ref bean="myPrecious"/>
            </list>
       </property>
    </bean>

    public static void setInstance(MyInterface instance) {
        theStaticInstance = instance;
    }
...which is not very pretty.

You can also use @Qualifier to disambiguate if you have multiple beans in your Spring container which implement MyInterface:
import org.springframework.beans.factory.annotation.Qualifier;
...
    private StaticHub(@Qualifier("myPrecious") MyInterface instance) {
        theStaticInstance = instance;
    }

Before you use this, consider carefully whether static fields are actually a good idea - in general this kind of pattern is something Spring is designed to help you avoid!

In my project there are good reasons for it, but it needs thought - things get complicated quickly in environments with multiple classloaders, or with multiple Spring containers inside the same classloader; it also makes it harder to use newfangled clustering technologies which let you scale to multiple JVMs.

7 comments:

  1. Nice work. I think there is also another difference when using the MethodInvokingFactoryBean. Where you can use the static method and not have the bean (that has the static method) in the container, the method is just used as initialization.

    Using your beans as example

    If you don't specify and just use the MethodInvokingFactoryBean declaration then since your static setInstance method returns void, there is no bean generated. I used this to initialize a class with just statics that i did not want in the container.

    ReplyDelete
  2. Looks like some of my comment got cut off since i used the xml notation. It should say

    If you don't specify bean class="very.public.api.StaticHub" name="dummyInstanceOfStaticHub" and just use .....

    ReplyDelete
  3. It is NOT working in Spring 3.1.3 . Who ever has it got worked are you using different Spring version than Spring 3.1.3 ..?

    ReplyDelete
  4. The project I was using was on Spring 3.0.0 - haven't tested it with anything else.

    ReplyDelete