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

时间:2015-02-06 20:13:56

标签: java spring spring-mvc dropwizard

我有一个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<HealthCheck>。)

6 个答案:

答案 0 :(得分:34)

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

请参阅BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry() method javadocs。这是完全您需要的东西,因为它允许您在加载正常的bean定义后修改Spring的应用程序上下文 但是 在任何单个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方式声明bean。

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

答案 1 :(得分:6)

你应该可以这样做:

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

}

答案 2 :(得分: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使用相同的实例。属性可见性设置为protected,以便子类可以使用创建的对象。

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

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

答案 3 :(得分:2)

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

我认为其他人没有想到的是,你曾经说过其他的bean会依赖于这些动态创建的bean。 这意味着必须在依赖bean之前实例化动态bean工厂。您可以使用

执行此操作(在注释世界中)
@DependsOn("myCleverBeanFactory")

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

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
   } 

...

答案 4 :(得分: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.
}

答案 5 :(得分:0)

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

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

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