How To Customize Spring Boot Auto Configuration
Spring Boot relies on auto configuration concept to automatically create and wire all the necessary beans required to set up a new application!
Overriding the default auto-configuration mechanism can help us control the logic of how a spring bean can be auto configured!
In this article, we are going to take a close look and explore the magic behind Spring Boot auto-configuration! We will see also, how to create an autoconfiguration class of our own as well!
What is Spring Boot auto configuration?
Auto-configuration is the feature used by Spring Boot to automatically configure a Spring application based on the dependencies you have already added!For example, If HSQLDB is present in your classpath and you did not configure a datasource bean manually, Spring Boot will simply auto configure it and add it to the context for you!
Sounds great, right?
With that being said, Spring boot can make your development process faster and easier beacause it has a lot of power when it comes to bootstrapping and configuring a new application!
The key to the auto-configuration magic is @EnableAutoConfiguration annotation that can be used to annotate the entrypoint class of your application!
This is how an entry class of a SpringBoot application looks like:
@SpringBootApplication
public class AutoConfigApplicationExample {
public static void main(String[] args) {
SpringApplication.run(AutoConfigApplicationExample.class );
}
}
@SpringBootApplication is equivalent to @Configuration, @EnableAutoConfiguration and @ComponentScan annotations all combined together.
From technical point of view, Spring Boot auto configuration is basically a simple Java configuration class annotated with @Configuration annotation and enriched with @Conditional* annotations!
@Configuration : used to specify that a class is a source of bean definitions!
@Conditional…: used to define some custom conditions on how some beans can be registred in the application context!
Built-in auto-configurations
Spring Boot provides multiple built-in configuration classes in spring-boot-autoconfigure module! Each class is responsible for registering and configuring specific beans.
You can check the following link to learn more about default configuration classes provided by Spring Boot: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-auto-configuration-classes.html
Let’s take a close look together on how DataSourceAutoConfiguration class is defined:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
...
...
...
static class EmbeddedDatabaseCondition extends SpringBootCondition {
private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";
private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("EmbeddedDataSource");
boolean hasDatasourceUrl = context.getEnvironment().containsProperty(DATASOURCE_URL_PROPERTY);
if (hasDatasourceUrl) {
return ConditionOutcome.noMatch(message.because(DATASOURCE_URL_PROPERTY + " is set"));
}
if (anyMatches(context, metadata, this.pooledCondition)) {
return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
}
EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
if (type == null) {
return ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll());
}
return ConditionOutcome.match(message.found("embedded database").items(type));
}
}
}
You can check the full code source of DataSourceAutoConfiguration class here!
@Conditional annotations
Conditional annotations let you register a bean conditionally, based on some criteria! They allow to define an auto configuration that is activated only when some conditions are met such as : Presence or absence of a specific class, bean, property…
Conditional annotations that SpringBoot use, are located in spring-boot-autoconfigure-{version}.jar, under the package org.springframework.boot.autoconfigure.condition
Spring provides many conditional annotations out-of-the-box! You can find for example:
@ConditionalOnClass : allow to trigger a bean configuration if the specified class is present in classpath!
@ConditionalOnMissingClass : used to create a spring bean if the specified class is not present!
@ConditionalOnBean : allow to activate a bean configuration if the specified bean is already defined in the context!
@ConditionalOnMissingBean : means that the configuration of a bean will be considered only if the specified bean is not defined!
@ConditionalOnProperty : used to conditionally load a bean depending on the configuration of a property!
@ConditionalOnResource : used to activate the configuration only when the specified resource is on the classpath!
Auto Configuration under the hood
spring.factories is where the magic of Spring Boot auto configuration is implemented! This file is located in META-INF/spring.factories of spring-boot-autoconfigure jar and contains several built-in configuration classes!
At startup, Spring Boot loads all the classes defined in spring.factories file and add them to its auto-configuration process which provide the application with everything it needs to run!
This is simple extract of spring.factories file:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
...
...
# 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.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
....
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
....
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
...
As you can see, the file contains a section called ”#Auto Configure”, which spans over a hundred of configuration classes that Spring Boot read on every application startup!
How to create a custom auto-configuration class?
As a developer, you may come across scenarios where you need to define and register some beans conditionally!
Sometimes, we need to customize how a bean can be created and auto configured in the application context!
So, to address this need, we have to create our own auto configuration class!
Custom Spring Boot auto configuration example
There is nothing better than explaining a concept with an example! So, what I’m going to do here, is create an auto-configuration that let an application generate a JSON or XML log file based on some conditions!
Let’s get into coding!
LogGenerator API:
It is just a sample interface with single method generate():
package com.azhwani.loggen.api;
public interface LogApi {
void generate(String name);
}
JSONLog implementation:
It is a class that holds the logic of generating JSON log files.
package com.azhwani.loggen.api;
public class JSONLog implements LogApi {
@Override
public void generate(String name) {
// TODO Auto-generated method stub
System.out.println("Generating JSON log file ..."+name+".json");
}
}
XMLLog implementation:
It is a class that defines the logic of generating XML log files.
package com.azhwani.loggen.api;
public class XMLLog implements LogApi {
@Override
public void generate(String name) {
// TODO Auto-generated method stub
System.out.println("Generating XML log file ..."+name+".xml");
}
}
LogGeneration auto-configuration:
We will try to implement a custom auto configuration that follows a simple scenario:
If XMLLog bean is defined in the application context, then the application will use it to generate an XML log file, otherwise it will use JSONLog by default to generate a JSON log file.
package com.azhwani.loggen.autoconfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.azhwani.loggen.api.JSONLog;
import com.azhwani.loggen.api.LogApi;
import com.azhwani.loggen.api.XMLLog;
@Configuration
@ConditionalOnClass(LogApi.class)
public class LogGenAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public JSONLog jsonLog() {
return new JSONLog();
}
@Bean
@ConditionalOnBean
public XMLLog xmlLog() {
return new XMLLog();
}
}
Now, the question is : How can spring boot know about our own auto-configuration class LogGenAutoConfiguration?
Well, the answer resides in spring.factories file! That simple file contains all of Spring Boot’s magic! You remmember it ?
Register our custom auto configuration
The next step is to tell spring where it can detect our auto-configuration, so we are going to add LogGenAutoConfiguration class into the spring.factories file!
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.azhwani.loggen.autoconfig.LogGenAutoConfiguration
Now, we need to declare a custom starter that holds our auto-configuration:
<groupId>com.azhwani</groupId>
<artifactId>autconfiguration-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
Demo:
This is how the entry class of our application looks like:
package com.azhwani.loggen;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.azhwani.loggen.api.LogApi;
@SpringBootApplication
public class LoggenApplication {
public static void main(String[] args) {
SpringApplication.run(LoggenApplication.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(LogApi logapi) {
return args -> { logapi.generate("dlog"); };
}
}
Scenario 1:
In this use case, we will not define JSONLog as a bean in the application context! So, based on @ConditionalOnMissingBean annotation, Spring Boot will create it and the application will use it to generate a JSON log file.
LogGenAutoConfiguration matched:
- @ConditionalOnClass found required class 'com.azhwani.loggen.api.LogApi' (OnClassCondition)
LogGenAutoConfiguration#jsonLog matched:
- @ConditionalOnMissingBean (types: com.azhwani.loggen.api.JSONLog; SearchStrategy: all) did not find any beans (OnBeanCondition)
Scenario 2:
We will annotate XMLLog class with @Service (and @Primary to avoid ambiguity) to register it as a spring bean! Since, we have marked xmlLog() method with @ConditionalOnBean, the application will simply use it to generate an XML log file.
LogGenAutoConfiguration matched:
- @ConditionalOnClass found required class 'com.azhwani.loggen.api.LogApi' (OnClassCondition)
LogGenAutoConfiguration#xmlLog matched:
- @ConditionalOnBean (types: com.azhwani.loggen.api.XMLLog; SearchStrategy: all) found bean 'XMLLog' (OnBeanCondition)
Conclusion
That’s all, in this write-up, we have explored how the magic of SpringBoot auto-configuration works and learned how to write a custom Spring Boot auto configuration class.
You can find the full source code of our example on GitHub!