开发者

How to extend already defined lists and maps in Spring application context?

Imagine a staged application context with different phases. We start with an early phase to define the necessary infrastructure. The xml application contexts are loaded sequentially.

The reason to split up these files is an extension/plugin mechanism.

Stage 01-default-configuration.xml

We prepare and declare the map with id exampleMapping to enhance them later with data.

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="[...]">

  <util:map id="exampleMapping" />
</beans>

Stage 02-custom-configuration.xml (optional)

We configure the exampleMapping and add an entry.

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="[...]">

  <util:map id="exampleMapping">
    <entry key="theKey" value="theValue" />
  </util:map>
</beans>

Stage 03-make-use-of-configuration.xml (mandatory)

Uses the defined map exampleMapping, whether it's configured customly or it's still the empty declared map.

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="[...]">

    <bean id="exampleService" class="com.stackoverflow.example.ExampleService">
       <property name="mapping" ref="exampleMapping" />
    </bean>
</beans>

The problem here is, that it's not possible to add entries to the exampleMappin开发者_开发技巧g map after the first stage. Spring throws an exception that the map with id exampleMapping already exists. If we leave out the first stage the map is undeclared and the third stage can't resolve exampleMapping which also produces an exception.

How can I solve this issue? I read Collection merging (spring docs) but this didn't helped. Is it possible to add values later to maps/lists before using them?

Thank you!


Both map and list has this attribute named merge=true|false for merging two lists. Alternatively you can use MethodInvokingFactoryBean for calling already defined list's add method to add extra items later.

Let's go over your example.

1) First the second scenario with MethodInvokingFactoryBean. Instead of defining the way you do, I defined your beans a little differently.

<bean class="java.util.HashMap" id="exampleMapping">
    <constructor-arg index="0">
        <map>
            <entry key="theKey" value="theValue"/>
        </map>
    </constructor-arg>
</bean>

<bean id="exampleService" class="com.stackoverflow.example.ExampleService">
    <property name="mapping" ref="exampleMapping"/>
</bean>

In another application content file, you can do the following to extend the map.

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="exampleMapping"/>
    <property name="targetMethod" value="putAll"/>
    <property name="arguments">
        <list>
            <map id="exampleMapping">
            <entry key="theKey2" value="theValue2"/>
            <entry key="theKey3" value="theValue3"/>
            <map>
        </list>
    </property>
</bean>

2) Now the first scenario. For this one, I just found something on page http://forum.springsource.org/showthread.php?t=53358

<bean id="commonData" class="A">
    <property name="map">
        <util:map>
            <entry key="1" value="1"/>
        </util:map>
    </property>
</bean>
<bean id="data" class="A" parent="commonData">
    <property name="map">
        <util:map merge="true">
            <entry key="2" value="2"/>
        </util:map>
    </property>
</bean>

Hope it helps.


You can define exampleMapping is the second definition is in a separate file, and you use <import resource="..."/> to import one file into another, but it's a fragile approach, and easily broken.

I suggest a more robust strategy. Replace the exampleMapping with a Registry class, which in turn contains and manages the mappings:

public MappingRegistry<K,V> {

   private final Map<K,V> mappings = new HashMap<K,V>();

   public void addMapping(K key, V value) {
      mappings.put(key, value);
   }

   public Map<K,V> getMappings() {
      return Collections.unmodifiableMap(mappings);
   }
}

Then, write a class which registers a mapping with the registry:

public class MappingRegistrar<K,V> {

   private final MappingRegistry<K,V> registry;

   private K key;
   private V value;

   @Autowired
   public MappingRegistrar(MappingRegistry<K,V> registry) {
      this.registry = registry;
   }

   public void setKey(K key) {
      this.key = key;
   }

   public void setValue(V value) {
      this.value = value;
   }

   @PostConstruct
   public void registerMapping() {
      registry.addMapping(key, value);
   }
}

The your config becomes something like this:

<bean id="mappingRegistry" class="com.xyz.MappingRegistry"/>

<bean id="mappingA" class="com.xyz.MappingRegistrar" p:key="keyA" p:value="valueA"/>
<bean id="mappingB" class="com.xyz.MappingRegistrar" p:key="keyB" p:value="valueB"/>
<bean id="mappingC" class="com.xyz.MappingRegistrar" p:key="keyC" p:value="valueC"/>

These mappings can now be scattered throughout your config in any way you see fit, and they will self-assemble. ExampleServcie is then injected with the MappingRegistry and extracts the mappings accordingly.

It's a bit more work than what you already have, but it's a lot more flexible and less error prone. This is particularly valuable if you're trying to build an extensible framework of some kind; you want to put fewer constraints on how people use it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜