Spring boot is an opinionated library that allows to create executable Spring applications with a convention over configuration approach.

The magic behind this framework lies in the @EnableAutoConfiguration annotation, which will automatically load all the beans the application requires depending on what Spring Boot finds in the classpath.

The @Enable* annotations

The @Enable... annotations are not new, they were first introduced in Spring 3 when the idea of replacing the XML files with java annotated classes is born.

A lot of Spring users already know @EnableTransactionManagement, which will enable declarative transaction management, @EnableWebMvc, which enables Spring MVC, or @EnableScheduling, which will initialize a scheduler.

These annotations are in fact a simple configuration import with the @Import annotation.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EnableAutoConfigurationImportSelector.class,
        AutoConfigurationPackages.Registrar.class })
public @interface EnableAutoConfiguration {

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     */
    Class<?>[] exclude() default {};

}

The EnableAutoConfigurationImportSelector uses SpringFactoriesLoader#loadFactoryNames of Spring core. SpringFactoriesLoader will look for jars containing a file with the path META-INF/spring.factories.

When it finds such a file, the SpringFactoriesLoader will look for the property named after our configuration file. In our case, org.springframework.boot.autoconfigure.EnableAutoConfiguration.

Let’s take a look at the spring-boot-autoconfigure jar, which indeed contains a spring.factories file copied below:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration

In this file, we can see a list of the Spring Boot auto-configurations. Let’s take a closer look at one of those configurations, MongoAutoConfiguration for instance:

@Configuration
@ConditionalOnClass(Mongo.class)
@EnableConfigurationProperties(MongoProperties.class)
public class MongoAutoConfiguration {

    @Autowired
    private MongoProperties properties;

    private Mongo mongo;

    @PreDestroy
    public void close() throws UnknownHostException {
        if (this.mongo != null) {
            this.mongo.close();
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public Mongo mongo() throws UnknownHostException {
        this.mongo = this.properties.createMongoClient();
        return this.mongo;
    }

}

This simple Spring configuration class declares typical beans needed to use mongoDb.

This classes, like a lot of others in Spring Boot relies heavily on Spring annotations:

  • @ConditionOnClass activates a configuration only if one or several classes are present on the classpath
  • @EnableConfigurationProperties automatically maps a POJO to a set of properties in the Spring Boot configuration file (by default application.properties)
  • @ConditionalOnMissingBean enables a bean definition only if the bean wasn’t previously defined

You can also refine the order in which those configuration classes load with @AutoConfigureBefore et @AutoConfigureAfter.

Properties Mapping

Let’s look at MongoProperties, which is a classic example of Spring Boot properties mapping:

@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {

    private String host;
    private int port = DBPort.PORT;
    private String uri = "mongodb://localhost/test";
    private String database;

    // ... getters/ setters omitted
}

The @ConfigurationProperties will associate every properties with a particular prefix to the POJO. For instance, the property spring.data.mongodb.port will be mapped to the port attribute of this class.

If you’re a Spring Boot user, I strongly encourage you to use those capabilities to remove the boiler plate code associated with configuration properties.

The @Conditional annotations

The power of Spring Boot lies in one of Spring 4 new features: the @Conditional annotations, which will enable some configuration only if a specific condition is met.

A sneak peek in the org.springframework.boot.autoconfigure.condition package in Spring Boot will give us an overview of what we can do with those annotations:

  • @ConditionalOnBean
  • @ConditionalOnClass
  • @ConditionalOnExpression
  • @ConditionalOnMissingBean
  • @ConditionalOnMissingClass
  • @ConditionalOnNotWebApplication
  • @ConditionalOnResource
  • @ConditionalOnWebApplication

Let’s take a closer look at @ConditionalOnExpression, which allows you to write a condition in the Spring Expression language.

@Conditional(OnExpressionCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ConditionalOnExpression {

    /**
     * The SpEL expression to evaluate. Expression should return {@code true} if the
     * condition passes or {@code false} if it fails.
     */
    String value() default "true";

}

In this class, we indeed make use of the @Conditional annotation. The condition is defined in the OnExpressionCondition class:

public class OnExpressionCondition extends SpringBootCondition {

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // ...
        // we first get a handle on the EL context via the ConditionContext

        boolean result = (Boolean) resolver.evaluate(expression, expressionContext);

        // ...
        // here we create a message the user will see when debugging

        return new ConditionOutcome(result, message.toString());
    }
}

In the end, the @Conditional are resolved to simple booleans, via the ConditionOutcome.isMatch method.

The ApplicationContextInitializers

The second possibility that the spring.factories file offers, is to define application initializers. They allow us to manipulate Spring’s applicationContext before the application is loaded.

In particular, they can create listeners on the context thanks to the ConfigurableApplicationContext#addApplicationListener method.

Spring Boot does that in the AutoConfigurationReportLoggingInitializer which listens to system events, like context refresh or the application’s failure to start. This will help create the auto-configuration report when you start your application in debug mode.

You can start your application in debug mode with either the -Ddebug flag or add the property debug=true to application.properties.

Debug Spring Boot Auto-Configuration

The documentation gives us some advice to understand what happened during the auto-configuration.

When launched in debug mode, Spring Boot will generate a report that looks like this one:

Positive matches:
-----------------

   MessageSourceAutoConfiguration
      - @ConditionalOnMissingBean (types: org.springframework.context.MessageSource; SearchStrategy: all) found no beans (OnBeanCondition)

   JmxAutoConfiguration
      - @ConditionalOnClass classes found: org.springframework.jmx.export.MBeanExporter (OnClassCondition)
      - SpEL expression on org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration: ${spring.jmx.enabled:true} (OnExpressionCondition)
      - @ConditionalOnMissingBean (types: org.springframework.jmx.export.MBeanExporter; SearchStrategy: all) found no beans (OnBeanCondition)

   DispatcherServletAutoConfiguration
      - found web application StandardServletEnvironment (OnWebApplicationCondition)
      - @ConditionalOnClass classes found: org.springframework.web.servlet.DispatcherServlet (OnClassCondition)


Negative matches:
-----------------

   DataSourceAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType (OnClassCondition)

   DataSourceTransactionManagerAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.jdbc.core.JdbcTemplate,org.springframework.transaction.PlatformTransactionManager (OnClassCondition)

   MongoAutoConfiguration
      - required @ConditionalOnClass classes not found: com.mongodb.Mongo (OnClassCondition)

   FallbackWebSecurityAutoConfiguration
      - SpEL expression on org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration: !${security.basic.enabled:true} (OnExpressionCondition)

   SecurityAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.security.authentication.AuthenticationManager (OnClassCondition)

   EmbeddedServletContainerAutoConfiguration.EmbeddedJetty
      - required @ConditionalOnClass classes not found: org.eclipse.jetty.server.Server,org.eclipse.jetty.util.Loader (OnClassCondition)

   WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#localeResolver
      - @ConditionalOnMissingBean (types: org.springframework.web.servlet.LocaleResolver; SearchStrategy: all) found no beans (OnBeanCondition)
      - SpEL expression: '${spring.mvc.locale:}' != '' (OnExpressionCondition)

   WebSocketAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.web.socket.WebSocketHandler,org.apache.tomcat.websocket.server.WsSci (OnClassCondition)

For each auto-configuration, we can see why it was initiated or why it failed.

Conclusion

Spring Boot’s approach leverages the possibilities of Spring 4 and allows to create an auto-configured executable jar.

Don’t forget that, as the documentation states, you can gradually replace the auto-configuration by declaring your own beans.

What I love about Spring Boot is that it allows you to prototype an application very quickly but also to learn with its source. Auto-configurations are neat pieces of code that can teach you a thing or two about Spring.

As Josh Long, developer advocate at Pivotal, said: