How Spring Boot Auto-Configuration Works

Deep immersion in the world of Spring Boot @Conditional annotation with well-developed examples of Mongo and MySQL database access class implementations .


In my post โ€œ Why Spring Boot? "The creation of a Spring Boot application was reviewed from which you can hardly understand what is going on behind the scenes. Perhaps you want to understand the Spring Boot auto-configuration magic.


Before that, you should learn about @Conditional annotation about Spring , which all Spring Boot auto-configuration magic depends on.


Exploring @Conditional Features


When developing applications based on Spring, you may need to conditionally register bean components.


For example, you can register a DataSource bean that points to the DEV database when the application starts locally, and point to another PRODUCTION database when working in a production environment.


You can transfer the database connection settings to the properties file and use the file that matches the environment, but you need to change the configuration whenever you need to specify a different environment and create an application.


To solve this problem, Spring 3.1 introduced the concept of Profiles (profile). You can register multiple beans of the same type and associate them with one or more profiles. When you start the application, you can activate the necessary profiles and components associated with the activated profiles, and only these profiles will be registered.


@Configuration
public class AppConfig
{
     @Bean
     @Profile("DEV")
     public DataSource devDataSource() {
        ...
     }

     @Bean
     @Profile("PROD")
     public DataSource prodDataSource() {
        ...
     }
}

You can then specify the active profile using the System Property - Dspring.profiles.active = DEV .


This approach works for simple cases, such as enabling or disabling component registration based on activated profiles. But if you want to register bean components based on some conditional logic, then the profile approach itself is not enough.


bean- Spring, Spring 4 @Conditional. @Conditional, , .


, , :


  • classpath
  • Spring bean ApplicationContext
  • /

, , .


, Spring's @Conditional.


, UserDAO . UserDAO, JdbcUserDAO, MySQL, MongoUserDAO, MongoDB.


JdbcUserDAO MongoUserDAO System Property, , dbType.


: java -jar myapp.jar -DdbType = MySQL, JdbcUserDAO. , : java -jar myapp.jar -DdbType = MONGO, MongoUserDAO.


JdbcUserDAO MongoUserDAO UserDAO :


public interface UserDAO
{
    List<String> getAllUserNames();
}
public class JdbcUserDAO implements UserDAO
{
    @Override
    public List<String> getAllUserNames()
    {
          System.out.println("**** Getting usernames from RDBMS *****");
          return Arrays.asList("Siva","Prasad","Reddy");
    }
}
public class MongoUserDAO implements UserDAO
{
    @Override
    public List<String> getAllUserNames()
    {
          System.out.println("**** Getting usernames from MongoDB *****");
          return Arrays.asList("Bond","James","Bond");
    }
}

Condition MySQLDatabaseTypeCondition, , System Property dbType ยซMYSQLยป :


public class MySQLDatabaseTypeCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
        String enabledDBType = System.getProperty("dbType");
        return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MYSQL"));
    }
}

Condition MongoDBDatabaseTypeCondition, , System Property dbType ยซMONGODBยป :


public class MongoDBDatabaseTypeCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
        String enabledDBType = System.getProperty("dbType");
        return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MONGODB"));
    }
}

bean- JdbcUserDAO MongoUserDAO, @Conditional :


@Configuration
public class AppConfig
{
    @Bean
    @Conditional(MySQLDatabaseTypeCondition.class)
    public UserDAO jdbcUserDAO(){
        return new JdbcUserDAO();
    }

    @Bean
    @Conditional(MongoDBDatabaseTypeCondition.class)
    public UserDAO mongoUserDAO(){
        return new MongoUserDAO();
    }
}

: java -jar myapp.jar -DdbType = MYSQL, bean- JdbcUserDAO.


System property: -DdbType = MONGODB, bean- MongoUserDAO.


, System Property.


, bean- MongoUserDAO , classpath Java MongoDB "com.mongodb.Server", , bean- JdbcUserDAO.


Condition MongoDB "com.mongodb.Server" :


public class MongoDriverPresentsCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata metadata)
    {
        try {
            Class.forName("com.mongodb.Server");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

public class MongoDriverNotPresentsCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
        try {
            Class.forName("com.mongodb.Server");
            return false;
        } catch (ClassNotFoundException e) {
            return true;
        }
    }
}

, bean- classpath.


bean- MongoUserDAO, Spring- UserDAO .


Condition, , - bean- , :


public class UserDAOBeanNotPresentsCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
         UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);
         return (userDAO == null);
    }
}

bean- MongoUserDAO, app.dbType = MONGO?


Condition :


public class MongoDbTypePropertyCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext,
    AnnotatedTypeMetadata metadata)
    {
        String dbType = conditionContext.getEnvironment()
                            .getProperty("app.dbType");
        return "MONGO".equalsIgnoreCase(dbType);
    }
}

Condition. Condition . Condition MYSQL MongoDB DatabaseType :


@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DatabaseTypeCondition.class)
public @interface DatabaseType
{
    String value();
}

DatabaseTypeCondition, DatabaseType :


public class DatabaseTypeCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext,
 AnnotatedTypeMetadata metadata)
    {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
        String type = (String) attributes.get("value");
        String enabledDBType = System.getProperty("dbType","MYSQL");
        return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));
    }
}

@DatabaseType :


@Configuration
@ComponentScan
public class AppConfig
{
    @Bean
    @DatabaseType("MYSQL")
    public UserDAO jdbcUserDAO(){
        return new JdbcUserDAO();
    }

    @Bean
    @DatabaseType("MONGO")
    public UserDAO mongoUserDAO(){
        return new MongoUserDAO();
    }
}

DatabaseType dbType , , .


, , , @Conditional.


Spring Boot @Conditional bean- .


Condition, SpringBoot org.springframework.boot.autoconfigure spring-boot-autoconfigure-{version}.jar.


Spring Boot @Conditional , .


?


, .


Spring Boot AutoConfiguration


Spring Boot โ€” @EnableAutoConfiguration. , , @SpringBootApplication, , , :


@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application
{

}

@EnableAutoConfiguration Spring ApplicationContext , .


Spring Boot AutoConfiguration spring-boot-autoconfigure-{version}.jar, .


AutoConfiguration @Configuration, Spring, @EnableConfigurationProperties .


, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.


@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration 
{
    ...
    ...
    @Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedConfiguration {

    }

    @Configuration
    @ConditionalOnMissingBean(DataSourceInitializer.class)
    protected static class DataSourceInitializerConfiguration {
        @Bean
        public DataSourceInitializer dataSourceInitializer() {
        return new DataSourceInitializer();
        }
    }

    @Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    protected static class NonEmbeddedConfiguration {
        @Autowired
        private DataSourceProperties properties;

        @Bean
        @ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
        public DataSource dataSource() {
            DataSourceBuilder factory = DataSourceBuilder
                    .create(this.properties.getClassLoader())
                    .driverClassName(this.properties.getDriverClassName())
                    .url(this.properties.getUrl()).username(this.properties.getUsername())
                    .password(this.properties.getPassword());
            if (this.properties.getType() != null) {
                factory.type(this.properties.getType());
            }
            return factory.build();
        }
    }
    ...
    ...
    @Configuration
    @ConditionalOnProperty(prefix = "spring.datasource", name = "jmx-enabled")
    @ConditionalOnClass(name = "org.apache.tomcat.jdbc.pool.DataSourceProxy")
    @Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class)
    @ConditionalOnMissingBean(name = "dataSourceMBean")
    protected static class TomcatDataSourceJmxConfiguration {
        @Bean
        public Object dataSourceMBean(DataSource dataSource) {
        ....
        ....
        }
    }
    ...
    ...
}

DataSourceAutoConfiguration @ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class}), , bean- DataSourceAutoConfiguration , DataSource.class EmbeddedDatabaseType.class classpath.


@EnableConfigurationProperties(DataSourceProperties.class), application.properties DataSourceProperties.


@ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
public class DataSourceProperties implements BeanClassLoaderAware, EnvironmentAware, InitializingBean {

    public static final String PREFIX = "spring.datasource";
    ...
    ...
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    ...
    //setters and getters
}

, spring.datasource.*, DataSourceProperties.


spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

bean-, SpringBoot, @ConditionalOnMissingBean, @ConditionalOnClass @ConditionalOnProperty ..


bean- ApplicationContext, .


spring-boot-autoconfigure-{version}.jar, :


  • org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
  • org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
  • org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
  • org.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationetc, etc.

I hope you now have an understanding of how Spring Boot autoconfiguration works using various autoconfiguration classes along with @Conditional features .


All Articles