簡體   English   中英

Spring - 以編程方式生成一組bean

[英]Spring - Programmatically generate a set of beans

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

所以你需要動態地聲明新的bean並將它們注入到Spring的應用程序上下文中,好像它們只是普通的bean,這意味着它們必須經過代理,后處理等,即它們必須服從Spring bean生命周期。

請參閱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...
}

這有效地聲明了您需要的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庫

  3. 您必須手動設置每個bean定義的所有屬性和依賴項。 每個bean定義都與其他bean定義無關,即你不能重用它們,將它們放在另一個bean中,等等。把它們想象成你用舊的XML方式聲明bean。

  4. 檢查BeanDefinitionBuilder javadocsGenericBeanDefinition 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之后檢索bean實際上是個好主意,如:

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

我會在這里籌碼。 其他人提到你需要創建一個注入配置的bean。 然后,該bean將使用您的配置創建其他bean並將它們插入到上下文中(您還需要以一種或另一種形式注入)。

我認為其他人沒有想到的是,你已經說過其他的bean將依賴於這些動態創建的bean。 這意味着必須在依賴bean之前實例化動態bean工廠。 你可以使用這個(在注釋世界中)

@DependsOn("myCleverBeanFactory")

至於你聰明的豆類工廠是什么類型的對象,其他人建議更好的方法來做到這一點。 但是,如果我沒記錯的話,你可以在舊的春天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
   } 

..

您需要創建一個基本配置類,該類由所有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.
}

我能提出的“最佳”方法是將所有Quartz配置和調度程序包裝在1個超級bean中並手動連接,然后重構代碼以使用uber bean接口。

uber bean在其PostConstruct中創建了我需要的所有對象,並實現了ApplicationContextAware,因此它可以自動連接它們。 這不是理想的,但它是我能想到的最好的。

Spring根本沒有一種以類型安全的方式動態添加bean的好方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM