Registering beans(prototype) at runtime in Spring

49,389

Solution 1

This is purely my opinion, not an expert view:

Spring provides two mechanisms for custom modification of an application context - using BeanFactoryPostProcessor which allows for modification of existing bean definitions or adding new bean definitions, and BeanPostProcessors which allows for modification of bean instances(wrapping them around proxy etc).

Spring does not provide any other native way to dynamically add bean definitions or bean instances at runtime, but like you have done by getting hold of the underlying bean factory instances and adding in bean definitions is one way to go. It works, but there are risks:

  • What happens if you overwrite an existing bean name with a new type, how are places where this bean is already injected handled. Also, what happens if a existing bean name is overwritten with a totally different type!

  • This newly registered bean will not have any fields autowired in, and will not be injected into other beans also - so essentially the bean factory is purely acting as a registry for holding the bean, not really a dependency injection functionality!

  • if a refresh() is called on the application context then the backing bean factory will be overwritten and a new one created, thus any bean instances registered against the bean factory directly will be lost.

If the objective is purely to create beans which has been autowired by Spring, I would go for something like @Configurable. If the risks above are acceptable also your approach should work.

Solution 2

This worked for me: http://random-thoughts-vortex.blogspot.com/2009/03/create-dynamically-spring-beans.html

Declare one dedicated Spring context bean, which will implement ApplicationContextAware and BeanFactoryPostProcessor interfaces:

  public class MyContextWrapper implements ApplicationContextAware,
             BeanFactoryPostProcessor {

   private ApplicationContext appContext;
   private ConfigurableListableBeanFactory factory;

   public void postProcessBeanFactory(ConfigurableListableBeanFactory factory)
              throws BeansException {
   this.factory = factory;
   }
   public void setApplicationContext(ApplicationContext c)
            throws BeansException {
   this.appContext = c;   
   }

   //setters and getters

}

Let spring load this bean in to it's context by declaring the bean in the XML configuration file:

<bean id="appContext" class="my.package.MyContextWrapper">
</bean>

Now this bean can be loaded in any other bean of the application via referencing it:

<bean id="myBeanFactory" class="my.package.MyBeanFactory">
 <property name="springContext" ref="appContext">
 </property>
</bean>

Use GenericBeanDefinition to load bean definition:

BeanDefinitionRegistry registry = ((BeanDefinitionRegistry )factory);

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MyBeanClass.class);
beanDefinition.setLazyInit(false);
beanDefinition.setAbstract(false);
beanDefinition.setAutowireCandidate(true);
beanDefinition.setScope("session");

registry.registerBeanDefinition("dynamicBean",beanDefinition);

Bean is created in session scope and will be stored in the user session. The property auto wire candidate tells spring if dependency's of the bean such as setter's or getter's or constructor argument's should be handled automatically by Spring. The property lazy init tells Spring if this bean should be instantiated when needed.

To get a handle of Spring bean, use Spring application context as follows:

Object bean= 
 getApplicationContext().getBean("dynamicBean");
 if(bean instanceof MyBeanClass){
 MyBeanClass myBean = (MyBeanClass) bean;

   // do with the bean what ever you have to do.
 } 
Share:
49,389
Khush
Author by

Khush

Team Lead - Australian Finance Group (AFG)

Updated on July 09, 2022

Comments

  • Khush
    Khush almost 2 years

    Just need something evaluated by the community. Following is a snippet of code, which is a simple factory that creates instances of a particular type. The method will register the bean in the context as a prototype and return the instance. This is the first time I am configuring beans at run time. Could you kindly evaluate and provide feedback? thank you in advance.

    package au.com.flexcontacts.flexoperations;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.config.ConstructorArgumentValues;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.support.AbstractApplicationContext;
    
    import au.com.flexcontacts.exceptions.SyncClassCreactionError;
    
    /**
     * @author khushroo.mistry
     * Class purpose: Simple Factory to create an 
     * instance of SynchroniseContactsService and register it in the Spring IoC.
     */
    public final class FLEXSyncFactory implements ApplicationContextAware {
    
        private static AbstractApplicationContext context;
    
    
        /**
         * @param username
         * @param password
         * @param syncType
         * @return the correct service class
         * @throws SyncClassCreactionError
         * The method registers the classes dynamically into the Spring IoC
         */
        public final SynchroniseContactsService createSyncService(String username, String password, SyncType syncType) throws SyncClassCreactionError {
    
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
    
            try {
    
                //Register the bean in the IoC
                BeanDefinition bdb = new GenericBeanDefinition();
                bdb.setBeanClassName(syncType.getClassName());
                bdb.setScope("prototype");
                ConstructorArgumentValues constructor = bdb.getConstructorArgumentValues();
                constructor.addIndexedArgumentValue(0, username);
                constructor.addIndexedArgumentValue(1, password);
                beanFactory.registerBeanDefinition(syncType.getInstanceName(), bdb);
    
                //Return instance of bean
                return (SynchroniseContactsService) beanFactory.getBean(syncType.getInstanceName());
            } catch (Exception e) {
                e.printStackTrace();
                throw new SyncClassCreactionError("Error: Illegal Handler");
            }
    
        }
    
        public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException {
            context = (AbstractApplicationContext) applicationContext;
    
        }
    
    }
    

    FLEX Sync factory has been configured in the IoC container as a singleton. So to create a new sync manager I do the following:

    flexSyncFactory.createSyncService(userName, password, SyncType.FULL);
    

    I am using Spring 3.1. Please review and provide your valuable feedback.

    kind regards.

  • Khush
    Khush almost 12 years
    I have discarded this approach as I did think that it was a bit risky. I have just used plain simple factory and reflection to generate these beans. These beans dont really need to be registered in the spring container. I will also investigate the @ Configuarable annotation. Thank you for evaluating my approach. Appreciate it.
  • VB_
    VB_ over 10 years
    @Biju Kunjummen I'm begginer in Spring, so correct me if I'm wrong. I speak of the phrase: Spring does not provide any other native way to dynamically add bean definitions or bean instances at runtime. What about StaticApplicationContext which allow you to add beans in runtime, and is using in plug-in architecture? Also what about custom BeanDefinitionReader mentioned at reference carlobonamico.com/blog/?p=55?