views:

585

answers:

4

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.

+2  A: 

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.

kdubb
But then there are two beans. "codebase" and the one in "containerSpecial.xml". From the application I only want to find one bean. And I don't want to make the bean "codebase" in "containerBase.xml" abstract, because it's needed from somewhere else.
tangens
You don't have to name both beans "codebase" they can have unique names. Also, you don't need to make the parent bean abstract that is not a requirement, only an option.
kdubb
By the way you also need to lookup "collection-merging". It solves the problem of needing to re-specify collection items that are already specified in the parent bean definition.
kdubb
But then I still have two beans of class "com.example.CodeBase". My Application calls XmlBeanFactory.getBeansOfType( CodeBase.class ). So I really want to have only one bean.
tangens
In that case use the abstract keyword on the parent bean as suggested in the reference example; bean definitions that are marked abstract will not be returned by getBeansOfType.You said in your first comment that the parent bean definition (which would now be abstract) is needed from somewhere else which is okay because being abstract doesn't disable it's use only it's instantiation. So wherever it's needed elsewhere you can make a new child definition that is also not abstract.
kdubb
+1  A: 

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.

Pablojim
Sorry can't get the formatting right...
Pablojim
+1  A: 

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.

jon077
I'm free do it the best way. Do you have an idea?
tangens
Properties files are a simple system configuration and can be injected into spring beans via Properties objects. I have used them for similar default/container properties. To get list support, delegate a method to parse the list from the Properties object.config.default.properties---------------------------source.locations.1=src/handmade/productivesource.locations.2=src/handmade/productive1config.container.properties---------------------------source.locations.1=src/container/productivesource.locations.2=src/container/productive1
jon077
+1  A: 

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>
Jason Gritman
CodebaseOverrider is not quite what I'm looking for, but with this approach I could write a CodebaseListExtender easily. I'll try it.
tangens