2015-02-06 61 views
13

我有一个Dropwizard应用程序,需要为配置列表中的每个配置生成十几个豆子。比如像健康检查,石英调度等Spring - 以编程方式生成一组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定义?

我会很好的一个子类解决方案或类型安全的任何东西,而不会让我复制和粘贴相同的代码,并重新命名方法有史以来我必须添加一个新的服务。

编辑:我要补充一点,我必须依赖于这些豆类其他组件(它们注入Collection<HealthCheck>例如)

+0

要么你需要用'BeanDefinitionRegistryPostProcessor'注册bean定义或者做一些上下文结构魔术(独立的环境中为用户的模块,其中的依赖关系可以在父上下文中自行注册),或只是做一个服务查找,而不是春天让以注入依赖关系(即'ApplicationContext#getBeansOfType')。 – 2015-02-12 22:33:10

回答

0

“最好”的方法,我能想出是包装所有的我Quartz配置和调度程序放在1 uber bean中,并手工完成,然后重构代码以使用uber bean接口。

尤伯杯豆创建,我需要在它的PostConstruct的所有对象,并实现了ApplicationContextAware因此它可以自动线他们。这并不理想,但它是我能想到的最好的。

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

4

你应该能够做这样的事情:

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

} 
+0

这是行不通的,因为我需要构建bean,将它们注入并使用它们来创建其他bean。 – noah 2015-02-12 21:00:17

0

您需要创建一个基础配置类,它可以被所有的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. 
} 
3

上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使用同一个实例。属性visibility设置为protected,以便子类可以使用创建的对象。

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

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

所以,你需要在即时宣布新豆,并将其注入到Spring的应用程序上下文,就好像他们只是普通这意味着它们必须经过代理,后处理等,即它们必须服从Spring bean的生命周期。

请参阅BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry() method javadocs。这是正是您需要的是什么,因为它可以让你修改Spring的应用程序上下文任何一个bean被实例之前正常bean定义已经加载后。

@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... 
} 

这有效地声明了你需要的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; 

    ... 

} 

注:

  1. 如果你去通过一个手动读取配置名称文件,请使用Spring的ClassPathResource.getInputStream()

  2. 如果你去通过自己扫描类路径中,我强烈建议您使用惊人Reflections library

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

  4. 查询BeanDefinitionBuilder javadocsGenericBeanDefinition javadocs了解更多详情。

1

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

我不认为其他任何人认识到的是,你说过其他bean将依赖于这些动态创建的bean。 这意味着您的动态bean工厂必须在从属bean之前实例化。您可以使用

@DependsOn("myCleverBeanFactory") 

至于什么类型的对象你灵巧的bean工厂是这样做(在标注的世界),其他人推荐这样做的更好的方法。 但是,如果我没记错的话,你能真正做到这一点的不老泉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 
    } 

..