3

My situation is that I have a properties-file to configure an unknown number of beans:

rssfeed.source[0]=http://feed.com/rss-news.xml
rssfeed.title[0]=Sample feed #1
rssfeed.source[1]=http://feed.com/rss-news2.xml
rssfeed.title[1]=Sample feed #2
:

I have a configuration class to read those properties:

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "rssfeed", locations = "classpath:/config/rssfeed.properties")
public class RssConfig {

  private List<String> source = new ArrayList<String>();
  private List<String> title = new ArrayList<String>();

  public List<String> getSource() {
    return source;
  }
  public List<String> getTitle() {
    return title;
  }

  @PostConstruct
  public void postConstruct() {

  }
}

This is working nicely. However, now I want to create beans based on that. What I've tried so far is

  1. add @Bean-methods and call them from postConstruct()

      @Bean
      @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
      public SourcePollingChannelAdapter createFeedChannelAdapter(int id, String url) {
        SourcePollingChannelAdapter channelAdapter = new SourcePollingChannelAdapter();
        channelAdapter.setApplicationContext(applicationContext);
        channelAdapter.setBeanName("feedChannelAdapter" + id);
        channelAdapter.setSource(createMessageSource(id, url));
        return channelAdapter;
      }
    
      @Bean
      @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
      public FeedEntryMessageSource createMessageSource(int id, String url) {
        try {
          FeedEntryMessageSource messageSource = new FeedEntryMessageSource(new URL(url), "");
          messageSource.setApplicationContext(applicationContext);
          messageSource.setBeanName("feedChannelAdapter" + id + ".source");
          return messageSource;
        } catch (Throwable e) {
          Utility.throwAsUncheckedException(e);
          return null;
        }
      }
    
      @Bean
      @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
      public QueueChannel createFeedChannel(int id, String url) {
        QueueChannel channel = new QueueChannel();
        channel.setApplicationContext(applicationContext);
        channel.setBeanName("feedChannel" + id);
        return channel;
      }
    
      @PostConstruct
      public void postConstruct() {
        for (int x = 0; x < source.size(); x++) {
          createFeedChannelAdapter(x, source.get(x));
        }
      }
    

    However, Spring tries to autowire the parameters to those methods rather than using the parameters I provided in postConstruct().

  2. a BeanFactoryPostProcessor or a BeanDefinitionRegistryPostProcessor. However, here I don't have access to the properties-file or the RssConfig-bean from above as it's called too early in the lifecycle.

What do I need to do generate those dynamic number of beans? I'm probably just a tiny little step away... I prefer a Java configuration-solution over an XML-solution.

sjngm
  • 12,423
  • 14
  • 84
  • 114

1 Answers1

4

You need to register the bean definitions (not call @Bean methods), so BeanDefinitionRegistryPostProcessor or ImportBeanDefinitionRegistrar are the best ways to do that currently. You can grab the properties file and bind to it using PropertiesConfigurationFactory (in Spring Boot) instead of using @ConfigurationProperties, or maybe you could use a parent context or a standalone SpringApplication to create and bind your RssConfig inside your bean definition registration code.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • 2
    Whow, the chief answered my question :) Yes, I got it to work with `PropertiesConfigurationFactory` and `BeanDefinitionRegistryPostProcessor`. The tough part was to scan through the source of the namespace handlers to set up the stuff manually. In case anyone reads this I recommend to create a typical XML-setup and then look at the bean registry to get an idea what all the important beans look like. – sjngm May 30 '14 at 20:57
  • Hi Dave, is there a better way to do this in 2020? – z atef Jan 31 '20 at 22:13
  • You would still have to extract the properties and create the beans. There is the functional bean registration API now (in `GenericApplicationContext`), so I guess that is a new option. – Dave Syer Feb 03 '20 at 16:35