Java 如何在运行时实例化 spring 管理的 bean?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27809838/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to instantiate spring managed beans at runtime?
提问by Vadim Kirilchuk
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:
我坚持从普通 java 到 spring 的简单重构。应用程序有一个“容器”对象,它在运行时实例化其部分。让我用代码解释一下:
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 的数量和配置的信息,然后根据给定的规范创建 bean。
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?
我们的对象已完全配置并注入了所有依赖项。但在我的情况下,我必须在执行 load() 方法后实例化一些也需要依赖注入的对象。我怎样才能做到这一点?
I am using java-based config. I already tried making a factory for RuntimeBeans:
我正在使用基于 Java 的配置。我已经尝试过为 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.htmlUnfortunately, 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 在所谓的“精简”模式下工作。http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html不幸的是,我发现简单地执行 new RuntimeBean() 没有区别;这是一个有类似问题的帖子: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.htmlbut it looks like a hammer in my case.
还有http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html但在我的情况下它看起来像一把锤子。
I also tried ApplicationContext.getBean("runtimeBean", args) where runtimeBean has a "Prototype" scope, but getBean is an awful solution.
我还尝试了 ApplicationContext.getBean("runtimeBean", args) 其中 runtimeBean 具有“Prototype”范围,但 getBean 是一个糟糕的解决方案。
Upd1.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);"
更新1。更具体地说,我试图重构这个类:https: //github.com/apache/lucene-solr/blob/trunk/solr/core/src/java/org/apache/solr/core/CoreContainer.java@参见#load() 方法并找到“return create(cd, false);”
Upd2.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
更新2。我在 spring 文档中发现了一个非常有趣的东西叫做“查找方法注入”:http: //docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-lookup-method -注射
And also an interesting jira ticket https://jira.spring.io/browse/SPR-5192where Phil Webb says https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-86051that javax.inject.Provider should be used here (it reminds me Guice).
还有一张有趣的 jira 票https://jira.spring.io/browse/SPR-5192,PhilWebb 说https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page=com.atlassian.jira .plugin.system.issuetabpanels:comment-tabpanel#comment-86051应该在这里使用javax.inject.Provider(它让我想起Guice)。
Upd3.There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html
Upd4.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
更新4。所有这些“查找”方法的问题是它们不支持传递任何参数..我还需要像使用 applicationContext.getBean("runtimeBean", arg1, arg2) 那样传递参数。看起来它在某个时候被修复了https://jira.spring.io/browse/SPR-7431
Upd5.Google Guice have a neat feature for it called AssistedInject. https://github.com/google/guice/wiki/AssistedInject
更新5。Google Guice 有一个简洁的功能,称为 AssistedInject。https://github.com/google/guice/wiki/AssistedInject
采纳答案by Vadim Kirilchuk
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.
看起来我找到了解决方案。由于我使用的是基于 Java 的配置,因此它比您想象的还要简单。xml 中的另一种方法是查找方法,但是仅从 spring 4.1.X 版开始,因为它支持将参数传递给方法。
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.
谢谢大家。
回答by mariubog
i think that your concept is wrong by usingRuntimeBean 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
我认为你的概念是错误的,因为RuntimeBean beanRuntime = createRuntimeBean();
你绕过 Spring 容器并使用常规的 java 构造函数,因此工厂方法上的任何注释都被忽略,并且这个 bean 永远不会被 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 .
这是在一种方法中创建多个原型 bean 的解决方案,看起来不漂亮但应该可以工作,我在 RuntimeBean 中自动装配容器作为自动装配的证据在日志中显示,您还可以在日志中看到每个 bean 都是原型的新实例,当您运行它时.
'
'
@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;
} '
回答by Anderson
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 infoas 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 staticway, let's say a static factory method.
您不需要 ,Container
因为所有运行时对象都应该由ApplicationContext
. 想想一个 Web 应用程序,它们大同小异。如上所述,每个请求都包含外部数据/环境信息。你需要的是一个原型/请求范围豆状ExternalData
或EnvironmentInfo
可通过读取和保持运行时数据静态的方式,比方说,一个静态工厂方法。
<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.
回答by walkeros
It is possible to register beans dynamically by using BeanFactoryPostProcesor
. Here you can do that while application is booting (spring's application context is beeing initialized). You can not register beans latet, but on the other hand you can make use of dependency injection for your beans, as they become "true" Spring beans.
可以使用 动态注册 bean BeanFactoryPostProcesor
。在这里,您可以在应用程序启动时执行此操作(spring 的应用程序上下文正在初始化)。您不能注册 bean latet,但另一方面,您可以对 bean 使用依赖注入,因为它们成为“真正的”Spring bean。
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.
如上所述,您仍然可以利用 Spring 的依赖注入,因为后处理器在实际的应用程序上下文上工作。
回答by Rzv Razvan
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:
您可以使用以下命令代替 SingletonBeanRegistry:
BeanFactory beanFactory = configContext.getBeanFactory();
Anyway SingletonBeanBuilder extends HierarchicalBeanFactory and HierarchicalBeanFactory extends BeanFactory
无论如何 SingletonBeanBuilder 扩展 HierarchicalBeanFactory 和 HierarchicalBeanFactory 扩展 BeanFactory