tags:

views:

1230

answers:

2

Hi,

our project is based on Spring IoC which provides easy extensibility, meaning the functionality of our project is extended by what we call extensions (or plug-ins) that provide a few additional Spring xml configuration files + new code.

The problem is I have to somehow distinguish between beans that have been loaded from different xml files.

Example: two extensions (let's call them A and B) installed two additional spring files: A.xml and B.xml. Both these files define beans of the same Java class. The framework loads all beans of this class (using autowiring) to some factory and later uses this list of beans in specific algorithms. But it should be able to know that what extension a bean is defined by.

Of course I can add an additional required property (for example name or code of the extension) for the beans and then extensions developers will have to fill it for every bean definition, but this is error prone: the property should be the same for all the beans of some particular extension, while it is filled for every bean. I'm looking for a more elegant solution that is less verbose.

Thanks.

+1  A: 

It sounds like each of your extensions should be defined within its own application context. Each of these contexts would share a single parent context, which would be your application "core".

This shoulkd give you an easier way of knowing which bean came from what, since you'd have to go through each context to obtain the beans to start with. Also, by isolating each extension in its own context, you reduce the possibility of beans clashing.

skaffman
Thanks. It sounds like an interesting idea. But how do I allocate a new context for each config file?
Stas
How do you identify and load the extension config files at the moment?
skaffman
Currently I just load config files using regular expressions. For example I am loading product-*-config.xml files, so if an extension deploys, say, product-myextension-config.xml file, it will be loaded. Seems like using your suggested approach I will have to make the naming convention stricter and predefine config file names for each extension separately so that I will be able to load them in different contexts. Which sounds reasonable. I'll try it, thanks!
Stas
After thinking a bit about the suggested approach I concluded I won't be able to benefit from the autowiring feature - the factories defines in the core application context will not autowire the beans defines in the children application context. This is very inconvenient for me :-(
Stas
+1  A: 

Configure each extension in a separate application context. In each application context, define a BeanPostProcessor which saves every bean the application context defines to a registry that maps beans to application contexts.

Configure an application context which will be the parent for each extension application context. In this configuration file, define a bean which maps bean names to application context identifiers.

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

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

</beans>

The extension application context configuration file defines an instance of BeanNameToApplicationContextIdMapInserter, which is a custom class implementing the BeanPostProcessor interface. The applicationContextId property is configured to a string identifying the application context. The map property configures the instance to update the map defined in the parent application context.

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

The BeanNameToApplicationContextIdMapInserter source code:

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

You can use SingletonBeanFactoryLocator to configure the parent application context and each extension application context.

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

Here is example code that reads the bean to application context map.

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"));
    }
}
Jim Huang
Thanks, sounds interesting, but (just like for another answer above): After thinking a bit about the suggested approach I concluded I won't be able to benefit from the autowiring feature - the factories defines in the core application context will not autowire the beans defines in the children application context. This is very inconvenient for me :-(
Stas