How to programmatically mark a spring bean as @Primary on startup?

Viewed 827

I have multiple DataSources in my application.

The standard org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration is annotated with @ConditionalOnSingleCandidate(DataSource.class)

I am attempting to select a @Primary DataSource programmatically.

I have tried a BeanFactoryPostProcessor that naively selects the first DataSource and marks as primary):

    @Bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor() {
        return this::setPrimaryDataSource;
    }

    public void setPrimaryDataSource(ConfigurableListableBeanFactory beanFactory) {

        // Get all DataSource bean names
        String[] dataSourceBeanNames = beanFactory.getBeanNamesForType(DataSource.class);

        // Find primaryBeanName
        String primaryBeanName = dataSourceBeanNames.length > 0 ? dataSourceBeanNames[0] : null;

        // Return appropriate bean
        assert primaryBeanName != null;
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(primaryBeanName);
        beanDefinition.setPrimary(true);
        LOGGER.info("Primary DataSource: {}", primaryBeanName);

    }

However, this does not appear to work - the @ConditionalOnSingleCandidate(DataSource.class) check on HibernateJpaConfiguration still fails.

Is there anywhere else I can put this code such that it will be executed before the check for @ConditionalOnSingleCandidate?

2 Answers

If your code is in a class with @Configuration, the method need to be static in order to update the bean definition before any bean creation.

Here is a sample for PropertySourcesPlaceholderConfigurer.

@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
         return new PropertySourcesPlaceholderConfigurer();
    }

}

In the documentation:

You may declare @Bean methods as static, allowing for them to be called without creating their containing configuration class as an instance. This makes particular sense when defining post-processor beans (for example, of type BeanFactoryPostProcessor or BeanPostProcessor), since such beans get initialized early in the container lifecycle and should avoid triggering other parts of the configuration at that point.

BeanFactoryPostProcessor worked for me:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        // logic to retrieve your bean name
        String beanName = beanFactory.getBeanNamesForType(MyService.class)[0];
        
        BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
        bd.setPrimary(true);
    }

}
Related