Christina
Christina

Reputation: 411

Spring conditional component scan configuration

I have a configuration class which registers beans based on a very simple condition (checking a property value in application.properties). The configuration class and the condition are the following:

@Configuration
@Conditional(DatabaseConfigurationCondition.class)
@ComponentScan(basePackageClasses = DBConfigComponents.class)
public class DatabaseConfigurationLoader {
    @Bean
    public DatabaseConfigurationRepository databaseConfigurationRepository() {
      return new DatabaseConfigurationRepository();
    }
}

and

public class DatabaseConfigurationCondition implements Condition {
  @Override
  public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
    return conditionContext.getEnvironment().getProperty("configuration.type").contains("db");
  }
}

In addition of the beans registered in this configuration class I have component scan which scans for other components. When the condition is not met, I expect the beans which are defined in the configuration class not to be registered (which happens to be a case), but also I expect other classes which are annotated with @Component (or @Repository, @Service, etc.. ) and are in same folder as DBConfigComponents.class marker interface not to be registered, which does not happen. Beans which are scanned are always registered, no matter if the condition is fulfilled or not.

When I put the @Conditional(DatabaseConfigurationCondition.class) on each @Component annotated class, than it's working correctly, but I don't want to put it on each class separately.

Any suggestion?

Upvotes: 4

Views: 9095

Answers (3)

The best way to achieve this is not to annotate these beans using @Component / @Service and @Repository annotations. Instead you should return these as part of the configuration you have setup which would be DatabaseConfigurationLoader. See sample below.

@Configuration
@Conditional(DatabaseConfigurationCondition.class)
public class DatabaseConfigurationLoader {
    @Bean
    public DatabaseConfigurationRepository databaseConfigurationRepository() {
        return new DatabaseConfigurationRepository();
    }

    @Bean
    public SomeService someService() {
        return new SomeService();
    }

    @Bean
    public SomeComponent someComponent() {
        return new SomeComponent();
    }
}

Note: Typically @Configuration with @Conditional are used in libraries that you want to include in your spring boot application. Such libraries should not share the same package as your spring boot application. Thus they should not be picked up by @ComponentScan annotation. Beans from libraries should not be annotated with @Component / @Service / @Repository annotations. Spring suggests using AutoConfiguration for that. See https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html & https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html

Upvotes: 2

Christina
Christina

Reputation: 411

Fortunately, I managed to fix this. The problem in my case was that I had another @ComponentScan annotation placed in other configuration class in other Maven module - not conditional on any property. The components which are in same package as DBConfigComponents marker interface were actually scanned by the other configuration class.

The way @ComponentScan works is on package level. Although, in different Maven modules, both configuration classes were in same package. @ComponentScan works perfectly fine with @Conditional. No need @Conditional to be placed on each component separately.

Upvotes: 2

Elgayed
Elgayed

Reputation: 1219

No need to implement Condition interface, you need to use '@ConditionalOnProperty' annotation:

@Configuration
@ComponentScan(basePackageClasses = DBConfigComponents.class)
@ConditionalOnProperty(name = "configuration.type", havingValue = "db")
public class DatabaseConfigurationLoader {
    @Bean
    public DatabaseConfigurationRepository databaseConfigurationRepository() {
      return new DatabaseConfigurationRepository();
    }
}

you can use 'prefix' instead of 'havingValue' depending on your needs.

Upvotes: 0

Related Questions