Различие между bean-компонентами, загруженными из разных файлов конфигурации Spring

наш проект основан на Spring IoC, который обеспечивает простую расширяемость, то есть функциональность нашего проекта расширяется за счет того, что мы называем расширениями (или подключаемыми модулями), которые предоставляют несколько дополнительных файлов конфигурации Spring xml + новый код.

Проблема в том, что мне нужно как-то различать bean-компоненты, загруженные из разных XML-файлов.

Пример: два расширения (назовем их A и B) установили два дополнительных файла spring: A.xml и B.xml. Оба эти файла определяют bean-компоненты одного и того же класса Java. Фреймворк загружает все bean-компоненты этого класса (используя автопроводку) в какую-то фабрику, а затем использует этот список bean-компонентов в определенных алгоритмах. Но он должен знать, каким расширением определяется bean-компонент.

Конечно, я могу добавить дополнительное обязательное свойство (например, имя или код расширения) для bean-компонентов, и тогда разработчикам расширений придется заполнять его для каждого определения bean-компонента, но это подвержено ошибкам: свойство должно быть одинаковым для всех bean-компоненты определенного расширения, в то время как он заполняется для каждого bean-компонента. Я ищу более элегантное решение, которое менее многословно.

Спасибо.


person Stas    schedule 07.06.2009    source источник


Ответы (2)


Похоже, что каждое из ваших расширений должно быть определено в собственном контексте приложения. Каждый из этих контекстов будет иметь общий родительский контекст, который будет вашим «ядром» приложения.

Это должно дать вам более простой способ узнать, какой bean-компонент пришел из чего, поскольку вам придется пройти через каждый контекст, чтобы получить bean-компоненты для начала. Кроме того, изолируя каждое расширение в его собственном контексте, вы уменьшаете вероятность конфликта компонентов.

person skaffman    schedule 07.06.2009
comment
Спасибо. Это звучит как интересная идея. Но как выделить новый контекст для каждого файла конфигурации? - person Stas; 07.06.2009
comment
Как вы определяете и загружаете файлы конфигурации расширения в данный момент? - person skaffman; 07.06.2009
comment
В настоящее время я просто загружаю файлы конфигурации, используя регулярные выражения. Например, я загружаю файлы product-*-config.xml, поэтому, если расширение развертывает, скажем, файл product-myextension-config.xml, он будет загружен. Похоже, что, используя предложенный вами подход, мне придется сделать соглашение об именах более строгим и предопределить имена файлов конфигурации для каждого расширения отдельно, чтобы я мог загружать их в разных контекстах. Что звучит разумно. Я попробую, спасибо! - person Stas; 08.06.2009
comment
Немного подумав о предложенном подходе, я пришел к выводу, что не смогу извлечь выгоду из функции автоматического подключения — фабрики, определяемые в контексте основного приложения, не будут автоматически подключать компоненты, определяемые в контексте дочернего приложения. Для меня это очень неудобно :-( - person Stas; 08.06.2009

Настройте каждое расширение в отдельном контексте приложения. В контексте каждого приложения определите BeanPostProcessor, который сохраняет каждый bean-компонент, определенный контекстом приложения, в реестр, который сопоставляет bean-компоненты с контекстами приложения.

Настройте контекст приложения, который будет родительским для каждого контекста приложения расширения. В этом файле конфигурации определите bean-компонент, который сопоставляет имена bean-компонентов с идентификаторами контекста приложения.

<!-- parent.xml -->
<beans>

  <bean
      id="beanNameToApplicationContextIdMap"
      class="java.util.HashMap"/>

</beans>

Файл конфигурации контекста приложения расширения определяет экземпляр BeanNameToApplicationContextIdMapInserter, который является пользовательским классом, реализующим интерфейс BeanPostProcessor. Свойство applicationContextId настроено на строку, идентифицирующую контекст приложения. Свойство map настраивает экземпляр для обновления карты, определенной в контексте родительского приложения.

<!-- alpha.xml -->
<beans>

  <bean class="com.example.BeanNameToApplicationContextIdMapInserter">
    <property name="applicationContextId" value="alpha"/>
    <property name="map" ref="beanNameToApplicationContextIdMap"/>
  </bean>

  <bean id="alphaService" class="...">
  </bean>

</beans>

Исходный код BeanNameToApplicationContextIdMapInserter:

public class BeanNameToApplicationContextIdMapInserter implements BeanPostProcessor {

    private String applicationContextId;
    private HashMap<String, String> map;

    public void setApplicationContextId(String id) {
        this.applicationContextId = id;
    }

    public void setMap(HashMap<String, String> map) {
        this.map = map;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName)
        throws BeansException
    {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName)
        throws BeansException
    {
        map.put(beanName, applicationContextId);
        return bean;
    }
}

Вы можете использовать SingletonBeanFactoryLocator для настройки контекста родительского приложения и контекста каждого дополнительного приложения.

<!-- beanRefFactory.xml -->
<beans>

  <bean
      id="parentApplicationContext"
      class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
       <value>parent.xml</value>
     </constructor-arg>
  </bean>

  <bean
      id="alphaApplicationContext"
      class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
       <value>alpha.xml</value>
     </constructor-arg>
     <constructor-arg>
       <ref bean="parentApplicationContext"/>
     </constructor-arg>
  </bean>

  <bean
      id="bravoApplicationContext"
      class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
       <value>bravo.xml</value>
     </constructor-arg>
     <constructor-arg>
       <ref bean="parentApplicationContext"/>
     </constructor-arg>
  </bean>

</beans>

Вот пример кода, который считывает карту контекста bean-компонента в приложение.

public class Main {

    private static ApplicationContext getApplicationContext(String name) {
        BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
        BeanFactoryReference bfr = bfl.useBeanFactory(name);
        return (ApplicationContext) bfr.getFactory();
    }

    public static void main(String[] args) {
        ApplicationContext parentApplicationContext =
                getApplicationContext("parentApplicationContext");
        HashMap<String, String> map = (HashMap<String, String>)
                parentApplicationContext.getBean("beanNameToApplicationContextIdMap");

        System.out.println(map.get("alphaService"));

        System.out.println(map.get("bravoService"));
    }
}
person Chin Huang    schedule 07.06.2009
comment
Спасибо, звучит интересно, но (как и в случае с другим ответом выше): немного подумав о предложенном подходе, я пришел к выводу, что не смогу извлечь выгоду из функции автоматического подключения - фабрики, определенные в контексте основного приложения, не будут автоматически подключаться beans определяет в контексте дочернего приложения. Для меня это очень неудобно :-( - person Stas; 08.06.2009