29

I stuck with a simple refactoring from plain Java to Spring. Application has a "Container" object which instantiates its parts at runtime. Let me explain with the code:

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

    public void load() {
        // repeated several times depending on external data/environment
        RuntimeBean beanRuntime = createRuntimeBean();
        runtimeBeans.add(beanRuntime);
    }

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

Basically, during load container asks some external system to provide him information about number and configuration of each RuntimeBean and then it create beans according to given spec.

The problem is: usually when we do in Spring

ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Container container = (Container) context.getBean("container");

our object is fully configured and have all dependencies injected. But in my case I have to instantiate some objects which also needs dependency injection after I execute load() method.
How can I achieve that?

I am using a Java-based config. I already tried making a factory for RuntimeBeans:

public class BeanRuntimeFactory {

    @Bean
    public RuntimeBean createRuntimeBean() {
        return new RuntimeBean();
    }
}

Expecting @Bean to work in so called 'lite' mode. http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html Unfortunately, I found no difference with simply doing new RuntimeBean(); Here is a post with a similar issue: How to get beans created by FactoryBean spring managed?

There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html but it looks like a hammer in my case.

I also tried ApplicationContext.getBean("runtimeBean", args) where runtimeBean has a "Prototype" scope, but getBean is an awful solution.


Update 1

To be more concrete I am trying to refactor this class: https://github.com/apache/lucene-solr/blob/trunk/solr/core/src/java/org/apache/solr/core/CoreContainer.java @see #load() method and find "return create(cd, false);"

Update 2

I found quite interesting thing called "lookup method injection" in spring documentation: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-lookup-method-injection

And also an interesting jira ticket https://jira.spring.io/browse/SPR-5192 where Phil Webb says https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-86051 that javax.inject.Provider should be used here (it reminds me Guice).

Update 3

There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html

Update 4

The issue with all these 'lookup' methods is they don't support passing any arguments.. I also need to pass arguments as I would do with applicationContext.getBean("runtimeBean", arg1, arg2). Looks like it was fixed at some point with https://jira.spring.io/browse/SPR-7431

Update 5

Google Guice have a neat feature for it called AssistedInject. https://github.com/google/guice/wiki/AssistedInject

informatik01
  • 16,038
  • 10
  • 74
  • 104
Vadim Kirilchuk
  • 3,532
  • 4
  • 32
  • 49
  • 1
    If you are instantiating the object with the new operator and a constructor, its not a Spring Bean and therefore its not eligible for DI. – Kevin Bowersox Jan 07 '15 at 01:26
  • 1
    Could you explain more about what you would like to do? – Kevin Bowersox Jan 07 '15 at 01:32
  • 1
    @KevinBowersox Not true if the object is being returned through a properly-intercepted `@Bean` method, such as on a configuration class. This sounds a lot like an XY problem, though, and something like Spring Cloud Connectors might be a better option. – chrylis -cautiouslyoptimistic- Jan 07 '15 at 01:39
  • @chrylis Agreed, Java Configuration requires the new operator. I was more referring to the `Container` class and its use of `new`. That's not going to fly with Spring. – Kevin Bowersox Jan 07 '15 at 01:55
  • You all are right, my question is how to redesign the code to make it work. What I would like to do? I would like to either somehow redisign the code to define runtime beans in configuration (but how if I even don't know number and properties for a beans) or to have some kind of factory which could create the beans at runtime with doing complete spring weaving... Actually, I don't know, I need someone to explain how to do things right. – Vadim Kirilchuk Jan 07 '15 at 11:36
  • Btw, what is XY problem? Thanks – Vadim Kirilchuk Jan 07 '15 at 11:58

5 Answers5

17

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.

lilalinux
  • 2,903
  • 3
  • 43
  • 53
Vadim Kirilchuk
  • 3,532
  • 4
  • 32
  • 49
  • Yes though your approach works only since 4.1.4.RELEASE, before you had to use simply getBean(name, ...args) on context or override interceptor on ConfigurationClassEnhancer in order to pass args to constructor – mariubog Jan 07 '15 at 23:02
  • Java based approach should work even on earlier versions, am I missing something? – Vadim Kirilchuk Jan 07 '15 at 23:24
  • 1
    sssBefore it did not accept arguments for constructor, it has been fixed since 4.1.4 – mariubog Jan 07 '15 at 23:31
  • 2
    Before it did not accept arguments for constructor, it has been fixed since 4.1.4. You have to remember that by calling return runtimeBean(beanName); you are not calling your method runtimeBean directly but rather call instantiating method on bean factory that is responsible for creating this bean in Spring context, and the arguments are resolved by factory before they are passed to the actual bean.In case of '@Bean' and '@Configuration' annotations the whole process is intercepted by ConfigurationClassEnhancer.BeanFactoryAwareMethodInterceptor and it decides how to instantiate your bean. – mariubog Jan 07 '15 at 23:39
  • The code above looks incomplete so difficult to understand... Where is the beanToInjectMethod() ? I do not see any constructor in Container taking this as input etc... Is your solution based on lookup method ? Thanks to clarify :) – greg Jul 21 '22 at 12:31
  • Maybe the name of factory should be `RuntimeBeanFactory`?I see both `BeanRuntimeFactory` and `RuntimeBeanFactory` in the sample code, which makes me confused. – liushuaikobe Sep 13 '22 at 09:59
8

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;

} '
mariubog
  • 1,498
  • 15
  • 16
  • This is perfectly explains how to make it work in a 'make it work' way and that's what I tried to achieve with RuntimeBeanFactory. However I feel like there should be a better solution. Thanks. – Vadim Kirilchuk Jan 07 '15 at 11:54
  • 1
    Another issue here is that my prototype scope bean is parametrized with some parameters. ObjectFactory#getObject() doensn't allow to pass any parameters. – Vadim Kirilchuk Jan 07 '15 at 16:02
  • This method works, unlike `BeanFactoryPostProcessor` based solutions. Thanks!! – Michael Böckling May 12 '20 at 15:42
6

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

RazvanParautiu
  • 2,805
  • 2
  • 18
  • 21
4

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.

Anderson
  • 2,496
  • 1
  • 27
  • 41
  • Thanks for your comment, I don't see much of a difference with factory of mine. It's still a runtime bean with prototype scope created by container on load/reload/etc. I do need a container since it's a core class of a framework I tried to refactor using spring. – Vadim Kirilchuk Jan 21 '16 at 18:12
3

It is possible to register beans dynamically by using BeanFactoryPostProcesor. Here you can do that while the application is booting (spring's application context has been initialized). You can not register beans latest, but on the other hand, you can make use of dependency injection for your beans, as they become "true" Spring beans.

public class DynamicBeansRegistar implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (! (beanFactory instanceof BeanDefinitionRegistry))  {
            throw new RuntimeException("BeanFactory is not instance of BeanDefinitionRegistry");
        }   
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

        // here you can fire your logic to get definition for your beans at runtime and 
        // then register all beans you need (possibly inside a loop)

        BeanDefinition dynamicBean = BeanDefinitionBuilder.    
             .rootBeanDefinition(TheClassOfYourDynamicBean.class) // here you define the class
             .setScope(BeanDefinition.SCOPE_SINGLETON)
             .addDependsOn("someOtherBean") // make sure all other needed beans are initialized

             // you can set factory method, constructor args using other methods of this builder
            
             .getBeanDefinition();

        registry.registerBeanDefinition("your.bean.name", dynamicBean);           

}

@Component
class SomeOtherClass {

    // NOTE: it is possible to autowire the bean
    @Autowired
    private TheClassOfYourDynamicBean myDynamicBean;

}

As presented above, you can still utilize Spring's Dependency Injection, because the post processor works on the actual Application Context.

Yaroslav Boichuk
  • 1,763
  • 20
  • 31
walkeros
  • 4,736
  • 4
  • 35
  • 47