Как расширить уже определенные списки и карты в контексте приложения Spring?
Представьте себе поставленный контекст приложения с разными фазами. Мы начинаем с раннего этапа, чтобы определить необходимую инфраструктуру. Контексты приложения 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>
Этап 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
, который также создает исключение.
Как я могу решить эту проблему? Я читал Слияние коллекций (spring docs), но это не помогло. Можно ли добавлять значения позже в карты/списки перед их использованием?
Спасибо!
Ответы
Ответ 1
Вы можете определить exampleMapping
, второе определение - в отдельном файле, и вы используете <import resource="..."/>
для импорта одного файла в другой, но это хрупкий подход и легко ломается.
Я предлагаю более надежную стратегию. Замените 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
и соответственно извлекает отображения.
Это немного больше работы, чем то, что у вас уже есть, но это намного более гибкое и менее подверженное ошибкам. Это особенно ценно, если вы пытаетесь создать расширяемую структуру какого-то рода; вы хотите уменьшить количество ограничений на то, как люди его используют.
Ответ 2
Оба map
и list
имеют этот атрибут с именем merge=true|false
для объединения двух списков. В качестве альтернативы вы можете использовать MethodInvokingFactoryBean
для вызова уже определенного метода добавления списка для добавления дополнительных элементов позже.
Перейдем к вашему примеру.
1) Сначала второй сценарий с MethodInvokingFactoryBean
. Вместо определения того, как вы это делаете, я немного определил ваш beans.
<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>
Надеюсь, что это поможет.