Spring – 以编程方式生成一组bean

我有一个Dropwizard应用程序,需要为配置列表中的每个配置生成十几个bean。 健康检查,石英调度等等。

像这样的东西:

@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(); } } 

我有多个MyConfiguration实例都需要像这样的bean。 现在我必须复制并粘贴这些定义,并为每个新配置重命名它们。

我可以以某种方式迭代我的配置类并为每个类生成一组bean定义吗?

我可以使用子类化解决方案或类型安全的任何东西,而不会让我复制并粘贴相同的代码,并在我必须添加新服务时重命名方法。

编辑:我应该补充一点,我有其他依赖这些bean的组件(例如,他们注入Collection 。)

所以你需要动态地声明新的bean并将它们注入到Spring的应用程序上下文中,好像它们只是普通的bean,这意味着它们必须经过代理,后处理等,即它们必须服从Spring bean生命周期。

请参阅BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()方法 javadocs。 这正是您所需要的,因为它允许您在加载普通bean定义之后 在实例化任何单个bean之前修改Spring的应用程序上下文。

 @Configuration public class ConfigLoader implements BeanDefinitionRegistryPostProcessor { private final List configurations; public ConfigLoader() { this.configurations = new LinkedList<>(); // TODO Get names of different configurations, just the names! // ie 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 // ie 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 // ie 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... } 

这有效地声明了您需要的bean并将它们注入到Spring的应用程序上下文中,每个配置都有一组bean。 您必须依赖某些命名模式 ,然后根据需要通过名称自动assembly您的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; ... } 

笔记:

  1. 如果您通过手动从文件中读取配置名称,请使用Spring的ClassPathResource.getInputStream()

  2. 如果你自己扫描类路径,我强烈建议你使用惊人的Reflections库 。

  3. 您必须手动设置每个bean定义的所有属性和依赖项。 每个bean定义都与其他bean定义无关,即你不能重用它们,将它们设置在另一个bean中等等。把它们想象成你用旧的XML方式声明bean。

  4. 检查BeanDefinitionBuilder javadocs和GenericBeanDefinition javadocs以获取更多详细信息。

你应该可以做这样的事情:

 @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); } } } 

只是扩展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(); } } 

需要注意的一点是,我将自定义bean创建为配置类的属性,并在@PostConstruct方法中初始化它们。 这样我就可以将对象注册为bean(因此@Autowire和@Inject按预期工作),稍后我可以在构造函数注入中为需要它的bean使用相同的实例。 属性可见性设置为protected,以便子类可以使用创建的对象。

由于我们持有的实例实际上不是Spring代理,因此可能会出现一些问题(方面没有触发等)。 注册后检索bean实际上是一个好主意,如:

 toBeInjected = new ToBeInjected(); String beanName = this.getClass().getSimpleName() + "_quartzConfiguration"; beanFactory.registerSingleton(beanName, toBeInjected); toBeInjected = beanFactory.getBean(beanName, ToBeInjected.class); 

我会在这里筹码。 其他人提到你需要创建一个注入配置的bean。 然后,该bean将使用您的配置创建其他bean并将它们插入到上下文中(您还需要以一种或另一种forms注入)。

我认为其他人没有想到的是,你已经说过其他的bean将依赖于这些动态创建的bean。 这意味着必须在依赖bean之前实例化动态bean工厂。 你可以使用这个(在注释世界中)

 @DependsOn("myCleverBeanFactory") 

至于你聪明的豆类工厂是什么类型的对象,其他人建议更好的方法来做到这一点。 但是,如果我没记错的话,你可以在旧spring的世界里做到这样的事情:

 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 } 

..

您需要创建一个基本配置类,该类由所有Configuration类扩展。 然后,您可以按如下方式迭代所有配置类:

 // Key - name of the configuration class // value - the configuration object Map configurations = applicationContext.getBeansWithAnnotation(Configuration.class); Set keys = configurations.keySet(); for(String key: keys) { MyConfiguration conf = (MyConfiguration) configurations.get(key); // Implement the logic to use this configuration to create other beans. } 

我能提出的“最佳”方法是将所有Quartz配置和调度程序包装在1个超级bean中并手动连接,然后重构代码以使用uber bean接口。

uber bean在其PostConstruct中创建了我需要的所有对象,并实现了ApplicationContextAware,因此它可以自动连接它们。 这不是理想的,但它是我能想到的最好的。

Spring根本没有一种以类型安全的方式动态添加bean的好方法。