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.
RuntimeBean
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:
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?
@Bean
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.
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);"
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).
There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html
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
Google Guice have a neat feature for it called AssistedInject. https://github.com/google/guice/wiki/AssistedInject
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.
2.1m questions
2.1m answers
60 comments
57.0k users