Java Spring - 以编程方式生成一组 bean
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28374000/
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
Spring - Programmatically generate a set of beans
提问by noah
I have a Dropwizard application that needs to generate a dozen or so beans for each of the configs in a configuration list. Things like health checks, quartz schedulers, etc.
我有一个 Dropwizard 应用程序,它需要为配置列表中的每个配置生成一打左右的 bean。诸如健康检查、石英调度程序等。
Something like this:
像这样的东西:
@Component
class MyModule {
@Inject
private MyConfiguration configuration;
@Bean
@Lazy
public QuartzModule quartzModule() {
return new QuartzModule(quartzConfiguration());
}
@Bean
@Lazy
public QuartzConfiguration quartzConfiguration() {
return this.configuration.getQuartzConfiguration();
}
@Bean
@Lazy
public HealthCheck healthCheck() throws SchedulerException {
return this.quartzModule().quartzHealthCheck();
}
}
I have multiple instances of MyConfiguration that all need beans like this. Right now I have to copy and paste these definitions and rename them for each new configuration.
我有多个 MyConfiguration 实例,它们都需要这样的 bean。现在我必须复制和粘贴这些定义并为每个新配置重命名它们。
Can I somehow iterate over my configuration classes and generate a set of bean definitions for each one?
我可以以某种方式迭代我的配置类并为每个类生成一组 bean 定义吗?
I would be fine with a subclassing solution or anything that is type safe without making me copy and paste the same code and rename the methods ever time I have to add a new service.
我可以使用子类化解决方案或任何类型安全的解决方案,而无需在每次必须添加新服务时复制和粘贴相同的代码并重命名方法。
EDIT: I should add that I have other components that depend on these beans (they inject Collection<HealthCheck>
for example.)
编辑:我应该补充一点,我有其他依赖于这些 bean 的组件(Collection<HealthCheck>
例如它们注入。)
采纳答案by noah
The "best" approach I could come up with was to wrap all of my Quartz configuration and schedulers in 1 uber bean and wire it all up manually, then refactor the code to work with the uber bean interface.
我能想到的“最佳”方法是将我所有的 Quartz 配置和调度程序包装在 1 个 uber bean 中并手动将它们连接起来,然后重构代码以使用 uber bean 接口。
The uber bean creates all the objects that I need in its PostConstruct, and implements ApplicationContextAware so it can auto-wire them. It's not ideal, but it was the best I could come up with.
uber bean 在其 PostConstruct 中创建我需要的所有对象,并实现 ApplicationContextAware 以便它可以自动连接它们。这并不理想,但这是我能想到的最好的。
Spring simply does not have a good way to dynamically add beans in a typesafe way.
Spring 根本没有一种以类型安全的方式动态添加 bean 的好方法。
回答by micha
You should be able to do something like this:
你应该能够做这样的事情:
@Configuration
public class MyConfiguration implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@PostConstruct
public void onPostConstruct() {
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
for (..) {
// setup beans programmatically
String beanName= ..
Object bean = ..
configurableBeanFactory.registerSingleton(beanName, bean);
}
}
}
回答by Mithun
You need to create a base configuration class which is extended by all your Configuration
classes. Then, you can iterate over all the configuration classes as follows:
您需要创建一个由所有Configuration
类扩展的基本配置类。然后,您可以按如下方式遍历所有配置类:
// Key - name of the configuration class
// value - the configuration object
Map<String, Object> configurations = applicationContext.getBeansWithAnnotation(Configuration.class);
Set<String> keys = configurations.keySet();
for(String key: keys) {
MyConfiguration conf = (MyConfiguration) configurations.get(key);
// Implement the logic to use this configuration to create other beans.
}
回答by Apokralipsa
Just expanding on Michas answer - his solution works if I set it up like this:
只是扩展 Michas 的答案 - 如果我这样设置,他的解决方案就有效:
public class ToBeInjected {
}
public class PropertyInjected {
private ToBeInjected toBeInjected;
public ToBeInjected getToBeInjected() {
return toBeInjected;
}
@Autowired
public void setToBeInjected(ToBeInjected toBeInjected) {
this.toBeInjected = toBeInjected;
}
}
public class ConstructorInjected {
private final ToBeInjected toBeInjected;
public ConstructorInjected(ToBeInjected toBeInjected) {
this.toBeInjected = toBeInjected;
}
public ToBeInjected getToBeInjected() {
return toBeInjected;
}
}
@Configuration
public class BaseConfig implements BeanFactoryAware{
private ConfigurableBeanFactory beanFactory;
protected ToBeInjected toBeInjected;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
@PostConstruct
public void addCustomBeans() {
toBeInjected = new ToBeInjected();
beanFactory.registerSingleton(this.getClass().getSimpleName() + "_quartzConfiguration", toBeInjected);
}
@Bean
public ConstructorInjected test() {
return new ConstructorInjected(toBeInjected);
}
@Bean
public PropertyInjected test2() {
return new PropertyInjected();
}
}
One thing to note is that I am creating the custom beans as attributes of the configuration class and initialising them in the @PostConstruct method. This way I have the object registered as a bean (so @Autowire and @Inject works as expected) and I can later use the same instance in constructor injection for beans that require it. The attribute visibility is set to protected, so that subclasses can use the created objects.
需要注意的一件事是,我正在创建自定义 bean 作为配置类的属性,并在 @PostConstruct 方法中初始化它们。通过这种方式,我将对象注册为 bean(因此 @Autowire 和 @Inject 按预期工作),稍后我可以在需要它的 bean 的构造函数注入中使用相同的实例。属性可见性设置为受保护,以便子类可以使用创建的对象。
As the instance that we are holding is not actually the Spring proxy, some problems may occur (aspects not firing etc.). It may actually be a good idea to retrieve the bean after registering it, as in:
由于我们持有的实例实际上不是 Spring 代理,因此可能会出现一些问题(方面未触发等)。在注册后检索 bean 实际上可能是一个好主意,如下所示:
toBeInjected = new ToBeInjected();
String beanName = this.getClass().getSimpleName() + "_quartzConfiguration";
beanFactory.registerSingleton(beanName, toBeInjected);
toBeInjected = beanFactory.getBean(beanName, ToBeInjected.class);
回答by Federico Peralta Schaffner
So you need to declare new beans on-the-fly and inject them into Spring's application context as if they were just common beans, meaning they must be subject to proxying, post-processing, etc, i.e. they must be subject to Spring beans lifecycle.
因此,您需要动态声明新 bean 并将它们注入 Spring 的应用程序上下文中,就好像它们只是普通 bean 一样,这意味着它们必须受代理、后处理等约束,即它们必须受 Spring bean 生命周期的约束.
Please see BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()
methodjavadocs. This is exactlywhat you are in need of, because it lets you modify Spring's application context after normal bean definitions have been loadedbutbefore any single bean has been instantiated.
请参阅BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()
方法javadocs。这正是您所需要的,因为它允许您在加载普通 bean 定义之后但在实例化任何单个 bean 之前修改 Spring 的应用程序上下文。
@Configuration
public class ConfigLoader implements BeanDefinitionRegistryPostProcessor {
private final List<String> configurations;
public ConfigLoader() {
this.configurations = new LinkedList<>();
// TODO Get names of different configurations, just the names!
// i.e. You could manually read from some config file
// or scan classpath by yourself to find classes
// that implement MyConfiguration interface.
// (You can even hardcode config names to start seeing how this works)
// Important: you can't autowire anything yet,
// because Spring has not instantiated any bean so far!
for (String readConfigurationName : readConfigurationNames) {
this.configurations.add(readConfigurationName);
}
}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// iterate over your configurations and create the beans definitions it needs
for (String configName : this.configurations) {
this.quartzConfiguration(configName, registry);
this.quartzModule(configName, registry);
this.healthCheck(configName, registry);
// etc.
}
}
private void quartzConfiguration(String configName, BeanDefinitionRegistry registry) throws BeansException {
String beanName = configName + "_QuartzConfiguration";
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzConfiguration.class).setLazyInit(true);
// TODO Add what the bean needs to be properly initialized
// i.e. constructor arguments, properties, shutdown methods, etc
// BeanDefinitionBuilder let's you add whatever you need
// Now add the bean definition with given bean name
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
private void quartzModule(String configName, BeanDefinitionRegistry registry) throws BeansException {
String beanName = configName + "_QuartzModule";
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzModule.class).setLazyInit(true);
builder.addConstructorArgReference(configName + "_QuartzConfiguration"); // quartz configuration bean as constructor argument
// Now add the bean definition with given bean name
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
private void healthCheck(String configName, BeanDefinitionRegistry registry) throws BeansException {
String beanName = configName + "_HealthCheck";
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HealthCheck.class).setLazyInit(true);
// TODO Add what the bean needs to be properly initialized
// i.e. constructor arguments, properties, shutdown methods, etc
// BeanDefinitionBuilder let's you add whatever you need
// Now add the bean definition with given bean name
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
// And so on for other beans...
}
This effectively declares the beans you need and injects them into Spring's application context, one set of beans for each configuration. You have to rely on some naming patternand then autowire your beans by namewherever needed:
这有效地声明了您需要的 bean 并将它们注入 Spring 的应用程序上下文中,每个配置一组 bean。你必须依赖一些命名模式,然后在需要的地方按名称自动装配你的 bean:
@Service
public class MyService {
@Resource(name="config1_QuartzConfiguration")
private QuartzConfiguration config1_QuartzConfiguration;
@Resource(name="config1_QuartzModule")
private QuartzModule config1_QuartzModule;
@Resource(name="config1_HealthCheck")
private HealthCheck config1_HealthCheck;
...
}
Notes:
笔记:
If you go by reading configuration names manually from a file, use Spring's
ClassPathResource.getInputStream()
.If you go by scanning the classpath by yourself, I strongly recommend you use the amazing Reflections library.
You have to manually set all properties and dependencies to each bean definition. Each bean definition is independant from other bean definitions, i.e. you cannot reuse them, set them one inside another, etc. Think of them as if you were declaring beans the old XML way.
Check BeanDefinitionBuilder javadocsand GenericBeanDefinition javadocsfor further details.
如果您从文件中手动读取配置名称,请使用 Spring 的
ClassPathResource.getInputStream()
.如果您自己扫描类路径,我强烈建议您使用令人惊叹的Reflections 库。
您必须手动为每个 bean 定义设置所有属性和依赖项。每个 bean 定义都独立于其他 bean 定义,即您不能重用它们,将它们设置在另一个中,等等。把它们想象成你正在用旧的 XML 方式声明 bean。
检查BeanDefinitionBuilder javadocs和GenericBeanDefinition javadocs以获取更多详细信息。
回答by Richard
I'll just chip in here. Others have mentioned that you need to create a bean, into which your config is injected. That bean will then use your config to create other beans and insert them into the context (which you'll also need injecting in one form or another).
我就在这凑钱。其他人提到您需要创建一个 bean,将您的配置注入其中。然后该 bean 将使用您的配置创建其他 bean 并将它们插入到上下文中(您还需要以一种或另一种形式注入)。
What I don't think anyone else has picked up on, is that you've said other beans will be dependant upon these dynamically created beans. This means that your dynamic bean factory must be instantiated before the dependant beans. You can do this (in annotations world) using
我认为其他人没有注意到的是,您已经说过其他 bean 将依赖于这些动态创建的 bean。这意味着您的动态 bean 工厂必须在依赖 bean 之前实例化。您可以使用(在注释世界中)执行此操作
@DependsOn("myCleverBeanFactory")
As for what type of object your clever bean factory is, others have recommended better ways of doing this. But if I remember correctly you can actually do it something like this in the old spring 2 world :
至于你的聪明豆工厂是什么类型的对象,其他人已经推荐了更好的方法来做到这一点。但是,如果我没记错的话,您实际上可以在旧的 Spring 2 世界中执行以下操作:
public class MyCleverFactoryBean implements ApplicationContextAware, InitializingBean {
@Override
public void afterPropertiesSet() {
//get bean factory from getApplicationContext()
//cast bean factory as necessary
//examine your config
//create beans
//insert beans into context
}
..
..