【问题标题】:How to extend already defined lists and maps in Spring application context?如何在 Spring 应用程序上下文中扩展已定义的列表和映射?
【发布时间】:2011-06-07 02:05:54
【问题描述】:

想象一个具有不同阶段的分阶段应用程序上下文。我们从早期阶段开始定义必要的基础设施。 xml 应用程序上下文按顺序加载。

拆分这些文件的原因是扩展/插件机制。

阶段 01-default-configuration.xml

我们准备并声明 ID 为 exampleMapping 的地图,以便稍后使用数据对其进行增强。

<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 (可选)

我们配置exampleMapping并添加一个条目。

<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 (强制)

使用定义的映射exampleMapping,无论是自定义配置还是仍然是空的声明映射。

<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>

这里的问题是,在第一阶段之后无法向exampleMapping 映射添加条目。 Spring 会抛出一个异常,即 id 为 exampleMapping 的地图已经存在。如果我们省略第一阶段,则地图未声明,第三阶段无法解析exampleMapping,这也会产生异常。

我该如何解决这个问题?我读了Collection merging(spring docs),但这没有帮助。是否可以稍后在地图/列表中添加值,然后再使用它们?

谢谢!

【问题讨论】:

  • 你说的“第一阶段后不能用条目增强”是什么意思?

标签: java spring dependency-injection


【解决方案1】:

maplist 都有这个名为 merge=true|false 的属性,用于合并两个列表。或者,您可以使用MethodInvokingFactoryBean 来调用已经定义的列表的 add 方法,以便以后添加额外的项目。

让我们看看你的例子。

1) 首先是MethodInvokingFactoryBean 的第二个场景。我没有定义您的工作方式,而是对您的 bean 进行了一些不同的定义。

<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>

在另一个应用程序内容文件中,您可以执行以下操作来扩展地图。

<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) 现在是第一个场景。对于这个,我刚刚在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>

希望对你有帮助。

【讨论】:

  • 你能提供一个简单的例子吗?不幸的是,我没有让它运行。
  • 我更新了答案并给出了两个选项的示例。
  • 我认为第一个场景对于这种方法来说不是一个很好的设计,而且对我来说听起来很脏。第二个有点令人困惑,我认为像 skaffmann 提到的那样编写注册表是最好的。感谢您的回答和解释!
  • 其实也是一样的,你让MappingRegistrar为你做,而不是自己调用put方法,反正我个人不喜欢编码这种东西,如果可以的话以声明方式进行。
  • 你是对的,但如果可以避免反射和使用 API 的不稳定方式,我会投票支持编码方式。在这种情况下,#putAll() 方法不会改变,但使用另一个 API 可能会不稳定,你不觉得吗?
【解决方案2】:

可以定义exampleMapping是第二个定义在一个单独的文件中,你使用&lt;import resource="..."/&gt;将一个文件导入另一个文件,但这是一种脆弱的方法,很容易被破坏。

我建议采用更稳健的策略。将exampleMapping 替换为Registry 类,该类又包含并管理映射:

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);
   }
}

然后,编写一个向注册表注册映射的类:

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);
   }
}

你的配置变成这样:

<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"/>

这些映射现在可以以任何您认为合适的方式分散在您的配置中,并且它们会自行组装。然后ExampleServcie 被注入MappingRegistry 并相应地提取映射。

它比您已经拥有的要多一些工作,但它更灵活,更不容易出错。如果您正在尝试构建某种可扩展的框架,这将特别有价值;您想减少人们如何使用它的限制。

【讨论】:

  • 我已经使用这种机制为特殊类型的过滤器/值提供者​​提供了可扩展的功能。认为这会带来很多开销,并且有一种更简单的方法可以为列表/地图实现这一目标。显然不是。我会按照你的建议实施它。也许我们将来会看到这样的弹簧特征?谢谢!
  • @codevour:我不认为这是矫枉过正,我认为这是一个很好的设计。在我看来,它比使用裸地图更健壮、更明确、更易读。
  • 你可能是对的,这可能是一种非常通用的方式,bean 也可以是匿名的。稍微考虑了一下,这很适合我的方法。再次感谢您。
猜你喜欢
  • 2011-11-30
  • 1970-01-01
  • 1970-01-01
  • 2019-05-13
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 2011-08-14
  • 2023-03-26
相关资源
最近更新 更多