spring-boot-autoconfiguration.adoc
spring-boot-autoconfiguration.adoc
Marco Behler
2020-12-26
:page-layout: layout-guides
:page-image: "/images/guides/undraw_cup_of_tea_6nqg.png"
:page-description: You can use this guide to get an in-depth understanding of what
Spring Boot's Autoconfigurations are and how they work.
:page-published: true
:page-tags: ["java", "spring boot", "spring", "spring boot autoconfiguration"]
:page-commento_id: /guides/spring-boot
:page-course_url: https://www.marcobehler.com/courses/spring-professional?
utm_campaign=spring_boot_autoconfiguration_guide&utm_medium=spring_boot_autoconfigu
ration_guide&utm_source=spring_boot_autoconfiguration_guide
:springbootversion: 2.3.4.RELEASE
(*Editor’s note*: At ~3500 words, you probably don't want to try reading this on a
mobile device. Bookmark it and come back later.)
== Introduction
Spring Boot's website offers the following answer: "Spring Boot takes an
opinionated view of the Spring platform and third-party libraries so you can get
started with minimum fuss."
I can give you a 99,99% guarantee, that you'd rather want to read the
https://www.marcobehler.com/guides/spring-framework[What is Spring Framework?]
article first, if...:
Take the time to finish the previous article first, then come back here.
Furthermore, if you are looking for some simple copy & paste Spring Boot
REST/microservice tutorials, I would advise you to go to https://spring.io/guides,
instead.
Let's start.
== Conditionals
Before you become a Spring Boot guru, you need to understand just _one_ very
important concept: Spring Framework's @Conditional annotation.
*Parental Advice*: Don't skip this section, as it is the basis for _everything_
that Spring Boot does. Also, I'll make it as interesting as possible.
Imagine you are working for ReallyBigCompany™ with a couple of teams working on
different Spring projects or products. But you are _not_ using Spring Boot, just
plain Spring Framework.
Also imagine that at one point, one of your developers had a great idea to extract
one common ApplicationContextConfiguration out of all projects, because she noticed
that every company project uses a few of the same beans.
[source,java]
----
@Configuration
public class ReallyBigCompanySharedContextConfiguration { // <1>
@Bean
public ReallyBigCompanyProprietaryFlywayClone flywayClone() {
return new ReallyBigCompanyProprietaryFlywayClone(); // <2>
}
}
----
1. This SharedContextConfiguration would live in its own project, eventually
published as its own .jar file that all your company's Spring projects can import
as a Maven/Gradle dependency.
2. Imagine that ReallyBigCompany built a proprietary Flyway clone, i.e. something
that initializes and manages databases. And as every project in your company uses a
relational database, it makes sense to extract it to this shared configuration.
[source,java]
----
@Configuration
@Import(ReallyBigCompanySharedConfiguration.class) // <1>
public class EarlyExitUnicornProjectContextConfiguration {
// <2>
}
----
1. Every company project from now on imports the SharedConfiguration, as it
contains so many goodies (well, only the Flyway clone at the moment, but you get
the point).
2. You would specify your project-specific beans here, as normal.
=== What is the problem with shared ApplicationContextConfigurations?
Having such a shared Spring configuration in your company works, but there's a
problem on the horizon.
What if you want to create another project which wants to import that shared
configuration, but _not_ have a ReallyBigCompanyProprietaryFlywayClone, because it
does not use a relational database?
Then you need a way to tell Spring: That @Configuration is fine, but please don't
create that one specific @Bean - just ignore it. Go ahead and create all other
@Beans though.
[source,java]
----
@Conditional(SomeCondition.class) // <1>
----
1. It has a "Condition" class parameter, which is a class that has a method called
"matches", returning a simple Boolean.
What does that mean for our SharedConfiguration? We could refactor it to look like
this:
[source,java]
----
@Configuration
public class ReallyBigCompanySharedContextConfiguration {
@Bean
@Conditional(IsRelationalDatabaseCondition.class) // <1>
public ReallyBigCompanyProprietaryFlywayClone flywayClone() {
return new ReallyBigCompanyProprietaryFlywayClone();
}
}
----
1. It is exactly the same ContextConfiguration as before, only now your @Bean
method is also annotated with a condition that we have yet to write.
What could this condition look like?
[source,java]
----
package com.marcobehler;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata
metadata) { // <1>
return oracleJdbcDriverOnClassPath() && databaseUrlSet(context); // <2>
}
Though you would very likely split this class up into two different conditions in
the real world, it highlights two very important conditions:
Even though we haven't covered any Spring Boot source code just yet, there is now a
new insight looming.
Yes, that (and not much more) is _exactly_ what Spring Boot is. Don't believe me?
Let's see some code.
mb_ad::spring_course[]
== AutoConfigurations
Everyone who has created a new Spring Boot application knows you'll end up with a
runnable main() method that launches Spring Boot's magic.
[source,java]
----
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MySpringBootApp {
}
----
1. You run this main method and suddenly your Tomcat server boots up, your
application.properties file gets read in and you can immediately start writing
@RestControllers.
There are many things happening when running your SpringApplication, but let's have
a look at three specific ones which, at first, don't have much in common, but
together form most of the basis of Spring Boot.
You can tell any plain Spring Framework application to read in .properties files
from basically any location you want, with the help of the @PropertySource
annotation.
[source,java]
----
@PropertySource(value = "classpath:application.properties", ignoreResourceNotFound
= true)
----
When you run the main method of your MySpringBootApp, Spring Boot will
_automatically_ register 17 of these PropertySources for you (even though not
correct, you can think of it as 17 of these annotations automatically added to your
project).
[source,console]
----
4. Command line arguments.
...
10. OS environment variables.
...
15. Application properties packaged inside your jar (application.properties and
YAML variants).
----
So, Spring Boot merely has a default set of property locations that it _always_
tries to read in, like command line arguments, or application.properties inside
your .jar file etc. Or, it ignores the location. That's it.
Now, what does it do with these .properties? Before we have a look at that in
Spring Boot's original source code, let's have a look at the second big thing that
happens when running a Spring Boot's main method.
mbimage::/images/guides/spring-boot/spring_factories.PNG[Spring Factories]
When you open up that file, there's one section called "#Auto Configure", which
spans over a hundred lines.
[source,console]
----
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfigura
tion,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguratio
n,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfigura
tion,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfi
guration,\
// 100+ more lines
----
But before we look at these AutoConfigurations, let's have one last look at one
additional Spring Boot feature.
A couple of paragraphs earlier, we saw that Spring Framework comes with the
@Conditional annotation. But that annotation is a bit low-level.
Spring Boot comes with its own set of additional @Conditional annotations, which
make developers' lives easier. (Note, that the following parameter values of the
@Conditional annotations are just an example)
So, in short, with Spring Boot you do not have to write the most common conditions
yourself (like checking for a property). Instead, you can use its enhanced
@Conditional annotations.
==== Summary
To follow along, you might want to checkout the Spring Boot project yourself. Don't
worry, it is just a quick 'git clone' and project import away.
Also note, that Spring Boot effectively switched to Gradle as build system in the
master branch, whereas older release branches are still Maven based.
[source,console]
----
git clone https://github.com/spring-projects/spring-boot.git
----
[source,java]
----
@Configuration(proxyBeanMethods = false) // <1>
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) // <2>
@EnableConfigurationProperties(DataSourceProperties.class) // <3>
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class }) // <3>
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
// some more
}
----
1. A DataSourceAutoConfiguration is a normal Spring @Configuration.
2. For the @Configuration to get evaluated further, you need to have two classes on
the classpath: DataSource and EmbeddedDatabaseType. If that Conditional is false,
the whole @Configuration is not evaluated.
3. We can ignore these two lines for now, but as a quick side-note:
@EnableConfigurationProperties enables that the properties you put into
your .properties files can get automatically set/converted to an object, like the
DataSourceProperties here.
4. The DataSourceAutoConfiguration has two other, inner, @Configurations. One of
them is the PooledDataSourceConfiguration, which will (conditionally) create a
connection pool DataSource for you.
5. It has a @Condition on a PooledDataSourceCondition, which really is a nested
@ConditionalOnProperty. Ouch!
6. The PooledDataSourceConfiguration only gets evaluated further if the user (i.e.
YOU) has not specified a DataSource or an XADataSource himself, yet.
7. The PooledDataSourceConfiguration imports quite a few _other_ configurations, to
be more specific: one configuration for each supported connection pool library
(Hikari, Tomcat, Dbcp2, etc.).
[source,java]
----
/**
* Hikari DataSource configuration.
*/
@Configuration(proxyBeanMethods = false) // <1>
@ConditionalOnClass(HikariDataSource.class) // <2>
@ConditionalOnMissingBean(DataSource.class) // <3>
@ConditionalOnProperty(name = "spring.datasource.type", havingValue =
"com.zaxxer.hikari.HikariDataSource", // <4>
matchIfMissing = true)
static class Hikari {
@Bean // <5>
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties,
HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
----
1. Another normal Spring @Configuration.
2. The HikariDataSource.class must be on the classpath, i.e. hikari-cp must be
added to your pom.xml/build.gradle file.
3. The user must _not_ have specified a DataSource bean himself.
4. Either the property "spring.datasource.type" is missing, or it must have a
specific value of "com.zaxxer.hikari.HikariDataSource".
5. If all these conditions match, then a good old Spring @Bean gets created. A
HikariDataSource. This is what you would otherwise have to create yourself, by
looking at HikariCP's documentation.
So, essentially all the DataSourceAutoConfiguration does is check for 3rd party
dependencies on the classpath and a couple of properties to be set. Then it boots
up a DataSource for you that you would otherwise have to configure yourself,
manually.
Back to the question from the beginning: How can Spring Boot boot up an embedded
Tomcat server by default? Simple:
The exercise for you is to find and understand those servlet specific auto-
configurations, with their appropriate conditionals.
And if you are done with that, you can have a look at the AutoConfiguration for one
of your favorite libraries, like Flyway, or Jackson, or MongoDB.
mb_ad::spring_course[]
== Dependency Management
One last piece of the puzzle is missing. How do all these dependencies get on the
classpath in Spring Boot projects? And how come you do not have to specify any
version numbers for your 3rd party libraries?
The one dependency every web-based Spring Boot project includes, is the spring-
boot-starter-web dependency. Its pom.xml file looks like this:
[source,xml]
----
<dependencies>
<!-- other dependencies left out for conciseness -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId> <!-- 1 -->
</dependency>
<!-- other dependencies left out for conciseness -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId> <!-- 2 -->
</dependency>
</dependencies>
----
1. Among other dependencies, the spring-boot-starter-tomcat dependency gets pulled
in transitively by the starter-web dependency.
2. Also, Spring WebMVC, Spring's web framework gets pulled in transitively.
[source,xml]
----
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId> <!-- 1 -->
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-annotations-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- other dependencies left out for conciseness -->
</dependencies>
----
1. This dependency is everything you need to start/run an embedded Tomcat server.
Spring Boot pulls it in, and guess what that means. That by default,
@ConditionalOnClass(Tomcat.class) conditions will be true! And that is exactly the
condition Spring Boot evaluates to start up an embedded Tomcat server. Not so much
rocket science, anymore, is it?
In general, by browsing through the starters, you'll find that the spring-boot-
starter-web pulls in 60+ other dependencies and third-party libraries into your
project - by default. From JSON and YAML libraries, to logging, to Spring's WebMVC
framework. It's all basic Maven/Gradle dependency management.
=== Why can you drop dependency versions in Spring Boot?
This leaves us with the last question. Why don't you (rather: almost never) have to
specify third-party dependency versions, when including them in your pom.xml file?
So, instead of this:
[source,xml]
----
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.12.Final</version>
</dependency>
----
[source,xml]
----
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
----
That is because the Spring Boot project that is generated by Spring's Initializr
extends from a parent project called spring-boot-dependencies. It is not so much of
a project, but a simple pom.xml file.
And in that pom.xml file, you have a _huge_ dependencyManagement section (covering
_every_ 3rd party library Spring Boot integrates with), which defines dependencies
that you can include in _your_ pom.xml file, without specifying the version number.
Because it is already specified in that parent pom.xml.
[source,xml]
----
<properties>
<hibernate.version>5.4.12.Final</hibernate.version> <!-- 1 -->
<!-- other versions left out for brevity -->
</properties>
That pom.xml file is the reason you do not have to specify 3rd party library
versions anymore. Unless of course, your specific 3rd party library is not in that
pom.xml file. Then you still need to define the version yourself.
mb_ad::spring_course[]
== FAQ
Say you didn't want Spring Boot to execute its DataSourceAutoConfiguration, you
could disable it in multiple ways:
[source,java]
----
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class)
public class Application {
[source,console]
----
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSource
AutoConfiguration
----
Though this means you need to roughly know which AutoConfiguration is responsible
for creating which @Beans, which you'll ultimately find out in Spring Boot's source
code.
So, for writing @RestControllers you still need to look at the official
https://docs.spring.io/spring/docs/current/spring-framework-reference/
web.html[Spring Web MVC documentation]. For Flyway,
https://flywaydb.org/documentation/[the Flyway documentation]. For Couchbase,
https://www.couchbase.com/[the Couchbase documentation].
And THEN you also need to know the autoconfigurations AND properties that Spring
Boot uses to configure these libraries. You can find a complete property list here:
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-
application-properties.html[Common Application properties]. Yes, it is a loooong
list.
After having read this article, you should understand that Spring Boot merely
preconfigures these tools for you with sane settings (i.e. let's always read in an
classpath:application.properties file and use the property server.port to
preconfigure a Tomcat server on a specific port). Hence, Spring Boot builds _on
top_ of Spring Framework.
Things get confusing, though, as the current marketing push is to call everything
Spring Boot or Spring Framework simply "Spring".
== Fin
Hopefully this article sheds some light on what Spring Boot's Autoconfiguration is.
Some final thoughts:
When it comes to building Spring Boot applications, you can roughly go two ways:
1. Recursively copy & paste your way through your Spring Boot projects with the
help of https://stackoverflow.com/[Stackoverflow] or
https://baeldung.com/[Baeldung].
2. Try to _understand_ what you are doing. That starts with understanding
https://www.marcobehler.com/guides/spring-framework[Spring Framework]. Then
understanding the 3rd party libraries (like Log4j 2 or Flyway). Then understanding
what Spring Boot does with all these little bits and pieces.
In case it was not clear, the second option is the only serious way to do it :)
== Acknowledgments