tangens
tangens

Reputation: 39733

How to modify beans defined in a spring container

I have two xml files defining beans for the springframework (version 2.5.x):

containerBase.xml:
<beans>
    <bean id="codebase" class="com.example.CodeBase">
        <property name="sourceCodeLocations">
            <list>
                <value>src/handmade/productive</value>
            </list>
        </property>
    </bean>
</beans>

... and

containerSpecial.xml:
<beans>
    <import resource="containerBase.xml" />
</beans>

Now I want to adjust the property sourceCodeLocations of bean codebase within containerSpecial.xml. I need to add a second value src/generated/productive.

A simple approach is to override the definition of codebase in containerSpecial.xml and add both values, the one from containerBase.xml and the new one:

containerSpecial.xml:
<beans>
    <import resource="containerBase.xml" />

    <bean id="codebase" class="com.example.CodeBase">
        <property name="sourceCodeLocations">
            <list>
                <value>src/handmade/productive</value>
                <value>src/generated/productive</value>
            </list>
        </property>
    </bean>
</beans>

Is there a way to extend the list without redefining the bean?

EDIT 2009-10-06:

The purpose of this is to have a shared standard container containerBase that is used by a lot of different projects. Each project can override/extend some properties that are special for that project in its own containerSpecial. If the project doesn't override, it's using the defaults defined in containerBase.

Upvotes: 12

Views: 17384

Answers (5)

Thrawn
Thrawn

Reputation: 11

In Spring 3.0, you can specify merge="true" on the 'list' tag. See http://forum.springsource.org/archive/index.php/t-97501.html for details.

Upvotes: 1

Jason Gritman
Jason Gritman

Reputation: 5301

You could use a BeanFactoryPostProcessor to change the bean's metadata before the Spring container instantiates the CodeBase bean. For example:

public class CodebaseOverrider implements BeanFactoryPostProcessor {

    private List<String> sourceCodeLocations;

    public void postProcessBeanFactory(
            ConfigurableListableBeanFactory beanFactory) throws BeansException {        
        CodeBase codebase = (CodeBase)beanFactory.getBean("codebase");
        if (sourceCodeLocations != null)
        {
            codebase.setSourceCodeLocations(sourceCodeLocations);
        }
    }

    public void setSourceCodeLocations(List<String> sourceCodeLocations) {
        this.sourceCodeLocations = sourceCodeLocations;
    }

}

Then in contextSpecial.xml:

<beans>
    <import resource="context1.xml" />

    <bean class="com.example.CodebaseOverrider">
        <property name="sourceCodeLocations">
            <list>
                <value>src/handmade/productive</value>
                <value>src/generated/productive</value>
            </list>
        </property>
    </bean>
</beans>

Upvotes: 11

Pablojim
Pablojim

Reputation: 8582

3 approaches:

  1. Simple: have two lists defaultSourceCodeLocations and additionalSourceCodeLocations and have your accessor methods check both of these (or combine them). I've seen this done in some frameworks - a default list of handlers is populated then additional user created ones are added...

  2. More complicated but keeps the original class clean: You could then create a CodeBaseModifier class. This would have a init-method to alter an injected instance of the bean.

    <bean id="codebaseModifier" class="com.example.CodeBase" init-method="populateCodeBase">
        <property name="sourceCodeLocations" ref="codebase"/>
        <property name="additionalSourceCodeLocations">
        <list>
            <value>src/handmade/productive</value>
        </list>
        </property>
    </bean>
    

If you wanted to make this really generic you could make a bean modifier that would do this by reflection. Be careful of the ordering if use this approach. Dependent beans of CodeBase would have to make sure this class was instantiated first (with depends on)

3 A variation on 2... Instead of directly creating a CodeBase class instead create a factory that returns a populated bean. This factory could then be configured with Spring in a similar fashion to 2. Have a defaultSourceCodeLocations and additionalSourceCodeLocations

Unless you need a lot of extensible properties I would go with option 1.

Upvotes: 1

jon077
jon077

Reputation: 10449

Is there a way to define the list in a properties or other configuration before hand?

It seems like the app configuration and wiring are tightly coupled. From my experience, if it is hard to do something in Spring, likely there is a different easier way to do it.

Upvotes: 1

kdubb
kdubb

Reputation: 483

Yes. A bean definition can have a "parent" attribute that references a parent bean definition. The new "child" definition inherits most of the properties of the parent and any of those properties can be overridden.

See Bean Definition Inheritance

Also you can use Collection Merging to merge the list property definition from the parent and child bean definitions. This way you can specify some list items in the parent bean definition and add more items to it in the child bean definition.

Upvotes: 3

Related Questions