Spring boot auto-configuration mystery revealed

Yannick FUSULIER
CodeShake
Published in
7 min readNov 12, 2018

This article is about giving you a bit more knowledge to better understand how spring boot auto-configuration works.

Spring boot is magic, it is amazing how quickly you can set it setup. The only required steps to create a micro-service are:

  • generate the project
  • add the “Web” dependency
  • annotate a class with @RestController
  • expose a web method

And that’s it!

Spring provides a tool that helps us generate a new project. For those who don’t know it already, there it is: https://start.spring.io/

Once you generate the project using the Spring Initializr tool, with the “Web” added as a dependency, you will get a pom.xml configuration file with the following dependency:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

And the main class that bootstraps the application:

@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

The remaining steps are achieved by defining the HelloControllerin the class DemoApplication :

@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

@RestController
public class HelloController {

@GetMapping("hello")
public String hello() {
return "hello";
}
}
}

Now you can build the application and run it:

mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar

The output indicates the endpoint is created:

2018-10-22 18:20:52.747  INFO 2580 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.example.demo.DemoApplication$HelloController.hello()

With the default configuration, the application starts with the context “/” at port 8080. The resource is then available at http://localhost:8080/hello and prints “hello” when being accessed.

Isn’t that impressive? Let’s continue and secure our resource by adding the dependency below in the pom.xml:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

If you try now to access the same URL, you should be asked to enter your credentials.

You can log in with the default user user and the password that was printed in the console:

Using generated security password: 5f7a6f4d-08c7-4bda-8265-a60c06c5bf24

What’s happening under the hood?

How is this all free? I did no configuration at all. I have only written 5 lines of code, put 2 dependencies and I have a running secured REST API.

Well, the secret resides in the file spring.factories.

I have never heard of it ! What’s that file?

The spring.factories file is a file that spring automatically loads when booting up. It contains the reference to many configuration classes.

The file is located in META-INF/spring.factories of the dependency org.springframework.boot:spring-boot-autoconfigure

There is an extract of the configuration that automatically enables the security layer:

org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\

The configuration classes must be defined in the following key:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

Let’s take a closer look into the class org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class,
UserDetailsService.class })
public class UserDetailsServiceAutoConfiguration {
....
}

Wow! That’s a lot of annotations!

You probably already know the@Configuration annotation. The @Configuration annotation tells the spring container that the annotated class defines a bunch of bean definitions. Those bean definitions will be used at runtime by the Spring container to populate the application context. The other annotations are less common. The conditional annotations are located in the spring-boot-autoconfigure dependency, under the package org.springframework.boot.autoconfigure.condition

The conditional annotations let you define configuration that are included when a condition is met. There are many conditions available:

ConditionalOnBean
ConditionalOnClass
ConditionalOnCloudPlatform
ConditionalOnExpression
ConditionalOnJava
ConditionalOnJndi
ConditionalOnMissingBean
ConditionalOnMissingClass
ConditionalOnNotWebApplication
ConditionalOnProperty
ConditionalOnResource
ConditionalOnSingleCandidate
ConditionalOnWebApplication

From the documentation:

@ConditionalOnClass(AuthenticationManager.class)

/**
* {
@link Conditional} that only matches when the specified classes are on the classpath.
*
*
@author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass

@ConditionalOnBean(ObjectPostProcessor.class)

/**
* {
@link Conditional} that only matches when beans of the specified classes and/or with
* the specified names are already contained in the {
@link BeanFactory}.
* <p>
....
*
*
@author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean

@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class })

/**
* {
@link Conditional} that only matches when no beans of the specified classes and/or
* with the specified names are already contained in the {
@link BeanFactory}.
* <p>
* ...

*
@author Phillip Webb
*
@author Andy Wilkinson
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean

See the code below, it’s the one that outputs the security password that was used to log in earlier:

if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n",
user.getPassword()));
}

How can I see which conditions are activated?

That will be the final part of this article. Spring made it easy to make sure we can answer this question. You can simply specify the property

debug=true

in the file src/main/resources/application.properties and restart the server. The output is now much longer because it contains the conditions evaluation reports:

============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
CodecsAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
...
...
Negative matches:
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition)
...
...
Exclusions:
-----------
None
...
...
Unconditional classes:
----------------------
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfigurationorg.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfigurationorg.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration

The conditional annotations contributed obviously to the simplicity of Spring Boot, but it is not the only mechanism!

Sprint Boot’s starters

The way the dependencies are organised contributed to it as well.

The pom.xmlconfiguration files are a lot much lighter than they used to be. I remember having to define almost every single individual dependency myself, making sure that those dependencies were compatible with one another. The Spring team made those problems vanish away when they introduced the starter projects. There is one starter for each type of application. If you want to use jpa repositories to interact with a database, you just need to include spring-boot-start-data-jpa. There are many starters available (https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter).

What are spring starters?

The starters are just maven projects that include the required dependencies for the module to work properly. In maven, all the dependencies of the dependencies you define in your project are part of your project’s dependencies. The command below prints all the dependencies of the project in the form of a tree.

mvn dependency:tree

All that complexity is hidden from us.

How comes we don’t have to specify the versions of the dependencies?

It’s thanks to the parent defined in the good old pom.xml configuration file.

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

The maven project inherits the configuration from its parent projects. Let’s open the pom.xml of spring-boot-start-parent .

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>

We can see that spring-boot-start-parent has itself a parent. There is an enormous list of properties and dependencies in the parent’s configuration file, including the spring-boot-starter-web and spring-boot-starter-security we defined earlier in our ownpom.xml configuration file.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>

Those dependencies are not defined in the <dependencies> section, but in the <dependencyManagement> section of the spring-boot-dependencies project. The difference between those sections is simple but important. The <dependencyManagement> section lets you pre-define the versions of the dependencies while those dependencies are not added in the project’s dependencies itself.

Dependency management — this allows project authors to directly specify the versions of artifacts to be used when they are encountered in transitive dependencies or in dependencies where no version has been specified. In the example in the preceding section a dependency was directly added to A even though it is not directly used by A. Instead, A can include D as a dependency in its dependencyManagement section and directly control which version of D is used when, or if, it is ever referenced.

https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

This mechanism has for effect that you do not need to specify the versions of the dependencies that have been defined in the <dependencyManagement> section later in the hierarchy.

Conclusion

That will be it for this article. I hope reading it helped you to be more confortable with the understanding of Spring Boot. Let me know if you think that some points should be clarified.

My final and last question would be:

Do you still consider Spring a mystery?

--

--