Loading...

March 9, 2009

Use Spring Configurator to support different dev, test and production environments with Spring configuration files (Part 8)

The previous parts(part 1, part 2, part 3, part 4, part 5, part 6, part 7) where all about the settings provided with Spring Configurator. In this part we look at another component that is part of the Spring Configurator: the BeanMap.

The BeanMap can store beans found in the Spring context in a map, hence the name. We can define the type of beans we want to store in the map and the BeanMap component will find those beans. The name or id of the found bean is used as the key for storing it in the map.

We extend our little example from the previous parts and introduce a simple interface (yes, a really simple, do-nothing, marker interface) named SampleService:

package com.mrhaki.netbeans.mavenapp;

public interface SampleService {
    // Simple marker interface.
}

Next we change the Sample and let it implement the SampleService interface:

package com.mrhaki.netbeans.mavenapp;

import org.apache.cocoon.configuration.Settings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Sample implements SampleService{

    private Settings settings;

    private String text;

    private int timeout = 1000;

    @Autowired
    public void setSettings(Settings settings) {
        this.settings = settings;
    }

    public void setText(String name) {
        this.text = name;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public String getMachine() {
        return settings.getProperty("machine");
    }
    
    @Override
    public String toString() {
        return "Running in [" + settings.getRunningMode() + "] mode. "
                + "Text = [" + text + "] and timeout = [" + timeout + "].";
    }
}

We can now define a BeanMap in our applicationContext.xml that needs to store all beans found in the Spring context which implement the SampleService interface. The BeanMap can be defined standalone and have it's own name or id or it can be assigned to a property of another bean. For this example we use it standalone. We need to set the attribute type to the type of class we want to find, which in our case is com.mrhaki.netbeans.mavenapp.SampleService:

<?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"
       xmlns:configurator="http://cocoon.apache.org/schema/configurator"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://cocoon.apache.org/schema/configurator http://cocoon.apache.org/schema/configurator/cocoon-configurator-1.0.1.xsd">

    <context:component-scan base-package="com.mrhaki.netbeans.mavenapp"/>
    <configurator:settings>
        <configurator:include-properties dir="classpath*:META-INF/app-config/properties"/> 
        <configurator:include-beans dir="classpath*:META-INF/app-config/spring"/> 
    </configurator:settings>

    <configurator:bean-map type="com.mrhaki.netbeans.mavenapp.SampleService" name="beanMap" />

    <bean name="sample" class="com.mrhaki.netbeans.mavenapp.Sample">
        <property name="text" value="${sample.text1:Default value}"/>
    </bean>

</beans>

In our test code we add statements to print out the key/value pairs of the BeanMap:

package com.mrhaki.netbeans.mavenapp;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppTest extends TestCase {

    public AppTest(String testName) {
        super(testName);
    }

    public static Test suite() {
        return new TestSuite(AppTest.class);
    }

    public void testApp() {
        final ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        final Map<String, Object> map = (Map) appContext.getBean("beanMap");
        for (String key : map.keySet()) {
          System.out.println("key[" + key + "] = value[" + map.get(key) + "].");
        }
    }
}

Now if we run our little test (mvn test) we can see in the following output both Sample beans are found:

key[anotherSample] = value[Running in [prod] mode. Text = [I am the other sample from anotherContext.xml.] and timeout = [1000].].
key[sample] = value[Running in [prod] mode. Text = [We are alive!] and timeout = [1000].].

Of course this is just a simple example, but if we assign the BeanMap as a property we can write application code which have access to all beans of a certain type defined in the Spring context. So we can access beans we don't know about, because they can be defined in a third-party jar, but still use them in our code.