How to instantiate Spring managed beans at runtime?

Looks like I found a solution. As I am using java based configuration it is even simpler than you can imagine. Alternative way in xml would be lookup-method, however only from spring version 4.1.X as it supports passing arguments to the method.

Here is a complete working example:

public class Container {
    private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
    private RuntimeBeanFactory runtimeBeanFactory;

    public void load() {
        // repeated several times depending on external data/environment
        runtimeBeans.add(createRuntimeBean("Some external info1"));
        runtimeBeans.add(createRuntimeBean("Some external info2"));
    }

    public RuntimeBean createRuntimeBean(String info) {
         // should create bean which internally can have some 
         // spring annotations or in other words
         // should be managed by spring
         return runtimeBeanFactory.createRuntimeBean(info);
    }

    public void setRuntimeBeanFactory(RuntimeBeanFactory runtimeBeanFactory) {
        this.runtimeBeanFactory = runtimeBeanFactory;
    }
}

public interface RuntimeBeanFactory {
    RuntimeBean createRuntimeBean(String info);
}

//and finally
@Configuration
public class ApplicationConfiguration {
    
    @Bean
    Container container() {
        Container container = new Container(beanToInject());
        container.setBeanRuntimeFactory(runtimeBeanFactory());
        return container;
    }
        
    // LOOK HOW IT IS SIMPLE IN THE JAVA CONFIGURATION
    @Bean 
    public BeanRuntimeFactory runtimeBeanFactory() {
        return new BeanRuntimeFactory() {
            public RuntimeBean createRuntimeBean(String beanName) {
                return runtimeBean(beanName);
            }
        };
    }
    
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    RuntimeBean runtimeBean(String beanName) {
        return new RuntimeBean(beanName);
    }
}

class RuntimeBean {
    @Autowired
    Container container;
}

That's it.

Thanks everyone.


i think that your concept is wrong by using
RuntimeBean beanRuntime = createRuntimeBean();
you are bypassing Spring container and resorting to using regular java constructor therefore any annotations on factory method are ignored and this bean is never managed by Spring

here is the solution to create multiple prototype beans in one method, not pretty looking but should work, I autowired container in RuntimeBean as proof of autowiring shown in log also you can see in log that every bean is new instance of prototype when you run this .

'

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        Container container = (Container) context.getBean("container");
        container.load();
    }
}

@Component
class Container {
    private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
    @Autowired
    ApplicationContext context;

    @Autowired
    private ObjectFactory<RuntimeBean> myBeanFactory;

    public void load() {

        // repeated several times depending on external data/environment
        for (int i = 0; i < 10; i++) {
            // **************************************
            // COMENTED OUT THE WRONG STUFFF 
            // RuntimeBean beanRuntime = context.getBean(RuntimeBean.class);
            // createRuntimeBean();
            // 
            // **************************************

            RuntimeBean beanRuntime = myBeanFactory.getObject();
            runtimeBeans.add(beanRuntime);
            System.out.println(beanRuntime + "  " + beanRuntime.container);
        }
    }

    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public RuntimeBean createRuntimeBean() {
        return new RuntimeBean();
    }
}

// @Component

class RuntimeBean {
    @Autowired
    Container container;

} '

You don't need the Container because all of the runtime objects should be created, held and managed by ApplicationContext. Think about a web application, they are much the same. Each request contains external data/environment info as you mentioned above. What you need is a prototype/request scoped bean like ExternalData or EnvironmentInfo which can read and hold runtime data through a static way, let's say a static factory method.

<bean id="externalData" class="ExternalData"
    factory-method="read" scope="prototype"></bean>

<bean id="environmentInfo" class="EnvironmentInfo"
    factory-method="read" scope="prototype/singleton"></bean>

<bean class="RuntimeBean" scope="prototype">
    <property name="externalData" ref="externalData">
    <property name="environmentInfo" ref="environmentInfo">
</bean> 

If you do need a container to save the runtime objects, code should be

class Container {

    List list;
    ApplicationContext context;//injected by spring if Container is not a prototype bean

    public void load() {// no loop inside, each time call load() will load a runtime object
        RuntimeBean bean = context.getBean(RuntimeBean.class); // see official doc
        list.add(bean);// do whatever
    }
}

Official doc Singleton beans with prototype-bean dependencies.


A simple approach:

@Component
public class RuntimeBeanBuilder {

    @Autowired
    private ApplicationContext applicationContext;

    public MyObject load(String beanName, MyObject myObject) {
        ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) applicationContext;
        SingletonBeanRegistry beanRegistry = configContext.getBeanFactory();

        if (beanRegistry.containsSingleton(beanName)) {
            return beanRegistry.getSingleton(beanName);
        } else {
            beanRegistry.registerSingleton(beanName, myObject);

            return beanRegistry.getSingleton(beanName);
        }
    }
}


@Service
public MyService{

   //inject your builder and create or load beans
   @Autowired
   private RuntimeBeanBuilder builder;

   //do something
}

Instead of using SingletonBeanRegistry you can use this:

BeanFactory beanFactory = configContext.getBeanFactory();

Anyway SingletonBeanBuilder extends HierarchicalBeanFactory and HierarchicalBeanFactory extends BeanFactory