Monday, 18 April 2011

Using a Spring ContextSingletonBeanFactoryLocator

When writing EJBs using Spring you often bump up against the ContextSingletonBeanFactoryLocator, a class that extends SingletonBeanFactoryLocator. As the name suggests, it’s used to create a singleton Spring context, and this is useful in EJB development as it seems preferable for all EJB instances in your bean pool to share the same Spring context: indeed I have fixed bugs in EJBs that were the result of loading one Spring context per EJB instance.

Having used this class in various projects, I thought that I’d figure out what it does under the hood.

    // Load the ContextSingleton
   
BeanFactoryLocator factoryLocator = ContextSingletonBeanFactoryLocator
        .getInstance
("classpath:beanRefContext.xml");

   
// Get hold of the factory to use
   
BeanFactoryReference ref = factoryLocator
        .useBeanFactory
("timerControlServiceBeanFactory");
    BeanFactory factory = ref.getFactory
();

   
// Get a bean from the factory
   
WineService instance = factory.getBean(WineService.class);
    System.out.println
("Loaded WineService: " + instance);

The first point to note is that you obtain a ContextSingletonBeanFactoryLocator using a getInstance() factory method underlining that this is a singleton class. By default, this loads any file named beanRefContext.xml that’s on your classpath, but you can use alternatively named XML files.

A beanRefContext can contain multiple ApplicationContext or factory instances, which can are referred to by their id.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
   <bean id="timerControlServiceBeanFactory" 
    class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
        <list>
          <value>classpath:spring-timer-service.xml</value>
          <value>classpath:spring-service.xml</value>
          <value>classpath:spring-transaction.xml</value>  
        </list>
     </constructor-arg>
   </bean>

   <bean id="flowControlServiceBeanFactory" 
    class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
        <list>
          <value>classpath:spring-flow-control-service.xml</value>
          <value>classpath:spring-service.xml</value>
          <value>classpath:spring-transaction.xml</value>  
        </list>
     </constructor-arg>
   </bean>

</beans>
Setting the factory name to null means that the useBeanFactory() will load any bean factory by type rather than by name / id and in this case, your beanrefcontext.xml file can contain only one ApplicationContext or factory instance.

    // Get hold of the factory to use
   
BeanFactoryReference ref = factoryLocator.useBeanFactory(null);

A beanRefContext.xml that loads by type:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context" 
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.0.xsd">

 <!-- This context is loaded by type and not by name -->
 <bean class="org.springframework.context.support.ClassPathXmlApplicationContext">
        <constructor-arg value="classpath:appConfig.xml" /> 
   </bean>

</beans>
Having got hold of the singleton factory, you can then access its beans in the usual way.

1 comment:

Sami Boudaya said...

Thank you very much for your post, it works!

I tried with the annontation @Interceptors(SpringBeanAutowiringInterceptor.class) but it didn't work.

It was to inject a Spring Service in a EJB class.