Since its release, Spring Boot has been a huge success: it boosts developers productivity with its convention over configuration philosophy. However, sometimes, it just feels too magical. I have always been an opponent of autowiring for this exact same reason. And when something doesn’t work, it’s hard to get back on track.

This is the reason why I wanted to dig deeper into Spring Boot starter mechanism - to understand every nook and cranny. This post is the first part and will focus on analyzing how it works. The second part will be a case study on creating a starter.

spring.factories

At the root of every Spring Boot starter lies the META-INF/spring.factories file. Let’s check the content of this file in the spring-boot-autoconfigure.jar. Here’s an excerpt of it:

...

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
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,\
...

Now let’s have a look at their content. For example, here’s the JpaRepositoriesAutoConfiguration class:

@Configuration
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class })
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled",
    havingValue = "true",  matchIfMissing = true)
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
@AutoConfigureAfter(HibernateJpaAutoConfiguration.class)
public class JpaRepositoriesAutoConfiguration {}

There are a couple of interesting things to note:

  1. It’s a standard Spring @Configuration class
  2. The class contains no "real" code but imports another configuration - JpaRepositoriesAutoConfigureRegistrar, which contains the "real" code
  3. There are a couple of @ConditionalOnXXX annotations used
  4. There seem to be a order dependency management of some sort with @AutoConfigureAfter

Points 1 and 2 are self-explanatory, point 4 is rather straightforward so let’s focus on point 3.

@Conditional annotations

If you didn’t start to work with Spring yesterday, you might know about the @Profile annotation. Profiles are a way to mark a bean-returning method as being optional. When a profile is activated, the relevant profile-annotated method is called and the returning bean contributed to the bean factory.

Some time ago, @Profile looked like that:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Profile {
    String[] value();
}

Interestingly enough, @Profile has been rewritten to use the new @Conditional annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
    String[] value();
}

Basically, a @Conditional annotation just points to a Condition. In turn, a condition is a functional interface with a single method that returns a boolean: if true, the @Conditional-annotated method is executed by Spring and its returning object added to the context as a bean.

Condition class diagram

There are a lot of conditions available out-of-the-box with Spring Boot:

Condition Description

OnBeanCondition

Checks if a bean is in the Spring factory

OnClassCondition

Checks if a class is on the classpath

OnExpressionCondition

Evalutates a SPeL expression

OnJavaCondition

Checks the version of Java

OnJndiCondition

Checks if a JNDI branch exists

OnPropertyCondition

Checks if a property exists

OnResourceCondition

Checks if a resource exists

OnWebApplicationCondition

Checks if a WebApplicationContext exists

Those can be combined together with boolean conditions:

Condition Description

AllNestedConditions

AND operator

AnyNestedConditions

OR operator

NoneNestedCondition

NOT operator

Dedicated @Conditional annotations point to those annotations. For example, @ConditionalOnMissingBean points to the OnBeanCondition class.

Time to experiment

Let’s create a configuration class annotated with @Configuration.

The following method will run in all cases:

@Bean
public String string() {
    return "string()";
}

This one won’t, for java.lang.String is part of Java’s API:

@Bean
@ConditionalOnMissingClass("java.lang.String")
public String missingClassString() {
    return "missingClassString()";
}

And this one will, for the same reason:

@Bean
@ConditionalOnClass(String.class)
public String classString() {
    return "classString()";
}

Analysis of the previous configuration

Armed with this new knowledge, let’s analyze the above JpaRepositoriesAutoConfiguration class.

This configuration will be enabled if - and only if all conditions are met:

@ConditionalOnBean(DataSource.class)

There’s a bean of type DataSource in the Spring context

@ConditionalOnClass(JpaRepository.class)

The JpaRepository class is on the classpath i.e. the project has a dependency on Spring Data JPA

@ConditionalOnMissingBean

There are no beans of type JpaRepositoryFactoryBean nor JpaRepositoryConfigExtension in the context

@ConditionalOnProperty

The standard application.properties file must contain a property named spring.data.jpa.repositories.enabled with a value of true

Additionally, the configuration will run after HibernateJpaAutoConfiguration (if the latter is referenced).

Conclusion

I hope I demonstrated that Spring Boot starters are no magic. Join me next week for a simple case study.