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.