Aditya Peshave
Aditya Peshave

Reputation: 667

@Conditional on bean initialization

I have been searching for this for a while.

Till now, I found this blog very useful but did not solve my problem.

I want to @Autowired a bean only if a flag is true, else I want that to be null

Use Case:

If one of the DB is under maintenance, I do not want my app to fail.

@Bean("sqlDatabase")
public Database getSqlDatabase(@Value("${datasource.sql.url}") String url,
        @Value("${datasource.sql.username}") String username, @Value("${datasource.sql.password}") String password,
        @Value("${datasource.poolsize.min}") int minPoolSize, @Value("${datasource.poolsize.max}") int maxPoolSize,
        @Value("${database.enable-sql}") boolean isSqlEnabled) {
    if (isSqlEnabled)
        return Database.builder().url(url).pool(minPoolSize, maxPoolSize).username(username).password(password)
                .build();
    else
        return null;
}

Now, in this case, its throwing error as I cannot autowire a null bean.

I wanted to use @Conditional but my case is a bit complex. I already need all 3 databases to be updated. I just want to skip one of them if conditions are not met.

Upvotes: 2

Views: 5580

Answers (2)

Mukundhan
Mukundhan

Reputation: 3457

We can leverage @Conditional property during the initial component scan to avoid error while initializing based on the environment properties.

A valid use case would be enable a repository to be scanned for bean initialization when the environment db.enabled property is true

example: https://javapapers.com/spring/spring-conditional-annotation/

Conditional helper class

public class DocumentDBInitializerPresence implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        DocumentDBInitializerInfo employeeBeanConfig = null;
        try {
            employeeBeanConfig = (DocumentDBInitializerInfo)context.getBeanFactory().getBean("DocumentDBInitializerInfo");
        } catch(NoSuchBeanDefinitionException ex) {
            System.out.println("BEAN NOT FOUND:: " + employeeBeanConfig != null );
        }
        System.out.println("BEAN FOUND :: " + employeeBeanConfig != null );
        boolean matches = Boolean.valueOf(context.getEnvironment().getProperty("db.enabled"));
        System.out.println("CONFIG ENABLED :: " + employeeBeanConfig != null );
        return employeeBeanConfig != null && matches;
    }
}

Using in service

@Service
@RefreshScope
@Conditional(value= DocumentDBInitializerPresence.class)
public class RepositoryDocumentDB {

    private static Logger LOGGER = LoggerFactory.getLogger(RepositoryDocumentDB.class);

    public static final String DOCUMENT_DB_LOCAL_HOST = "localhost";
    DocumentDBInitializerInfo documentDbConfig;

    @Autowired
    public RepositoryDocumentDB(final DocumentDBInitializerInfo documentDbConfig) {
        this.documentDbConfig = documentDbConfig;
    }
}

This will not throw error on application startup if you are not autowiring RepositoryDocumentDB anywhere yet and the db.enabled is set to false.

Hope this helps!

Upvotes: 1

You can use profiles. One profile for every database

  • db1
  • db2
  • db3

Than annotate the bean class or the bean method with the profile that must be activated to use that bean like

@Profile("db1")
@Bean("db1")
public Database getSqlDatabase(...){...}       

When you start your app, beans annotated with @Profile will only be created, if the regarding profile is activated.

You activate a profile by setting the property 'spring.profiles.active'. To activate db1 and db2 :

spring.profiles.active=db1,db3

You can set that property in a properties file or as a command line parameter.

Profiles give you a lot of flexibility to change you spring context by configuration

  • you can annotate many beans with the same profile
  • you can annotate a configuration class with a profile
  • you can use profile specific property files
  • you can use many profiles in one @Profile annotations. Logical 'or' will be used, so a bean annotated with @Profile("db1","db2") will be created if profile 'db1' is active or profile 'db2' is active
    • if you want something else than 'or' you can use @Conditional to define your own logic

Please note : If you use do not use component scan or xml configuration, the annotation @Profile at a bean class has no effect. You need to annotate the bean method with @Profile or the whole configuration class instead.

Upvotes: 3

Related Questions