Details of Spring Boot AutoConfiguration

What is the Spring Boot? How does Spring Boot Autoconfiguration work? This post will dive into the details of Spring Boot Autoconfiguration.

What is the Spring Boot?

Spring Boot website says “We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.

Spring Boot is a framework to build applications.

Spring Boot provides different features and if you are using it to build your application, you will need different beans. So, autoconfiguration automatically configures the Spring Boot application by creating these beans.

Why you use autoconfiguration?

Efficiency and time. With autoconfiguration, Spring will do a lot of lifting for developers and will save time from creating the beans.

Behind the scenes, it is basically a bunch of @Configuration classes. These classes don’t use the annotation @Configuration .

Some of the annotations these classes use are:

  • @ConditionalOnClass – The application uses this only if the given class is on the classpath.
  • @Conditional – Only if a condition is met
  • @ConditionalOnMissingBean – The application uses this if a bean is missing or not created.

In short, @Conditional annotation is the base of all annotations.

How do you really understand this?

You or your team are working on multiple projects and these projects share some common code.  If you want to extract this common code in its own library or shared beans, so all projects can use them.


@Configuration
public class SharedObjects
{
   @Bean
   public CommonObject commonObject()
   {
      return new CommonObject();
   }
}

Once this CommonObject is shared through a jar file, other projects can import it.

The drawback with this approach is if the other project wants to use CommonObject, but don’t want to use any other beans from that common code. Importing those beans would be unnecessary overhead in the project during startup. Therefore, you need a way to tell Spring that we only need CommonObject Bean and not other beans, don’t even create other beans. That’s when we can use @Conditional annotation.

To use this @Conditional annotation, there are a few ways. Spring Boot provides Condition interface that a class can implement.


public class IsBrowserOnCondition implements Condition
{
   @Override
   public boolean matches(ConditionContext context, AnotatedTypeMetadata metadata)
   {
      return isMozillaFirefoxEnabled(context);
   }
   
   public boolean isMozillaFirefoxEnabled(ConditionContext context)
   {
      return context.getEnvironment().containsProperty("spring.preferredbrowser");
   }
}

In this class IsBrowserOnCondition , we see the implementation of interface Condition.

  • This implementation includes the method matches .
  • This method calls another method to check if the Mozilla Firefox browser has been enabled.
  • In the process, it checks for a property spring.preferredbrowser condition.
  • Now if we want to create new beans on the condition, we will use the annotation @Conditional as @Conditional(IsBrowserOnCondition.class).

In short, Spring Boot is a shared context configuration with a number of beans created using annotation @Conditional.

AutoConfiguration with Spring Boot

To understand more about autoconfiguration, we will use a simple Spring Boot application. We want to know what happens when we start this application.

So the main class of this application will look like below:


package com.betterjavacode.abccompany

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

@SpringBootApplication
@EnableJdbcHttpSession
public class HomeApplication extends SpringBootServletInitializer
{
 public static void main(String[] args)
 {
  SpringApplication.run(HomeApplication.class, args);
 }
}

When I run this main class, Spring Boot starts up the tomcat webserver.



.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

2020-06-20 16:38:20.317  INFO 19632 --- [           main] c.rentersfeedback.home.HomeApplication   : Starting HomeApplication on YMALI2019 with PID 19632 (C:\projects\rentersfeedback\out\production\classes started by Yogesh Mali in C:\projects\rentersfeedback)
2020-06-20 16:38:20.320  INFO 19632 --- [           main] c.rentersfeedback.home.HomeApplication   : No active profile set, falling back to default profiles: default
2020-06-20 16:38:21.483  INFO 19632 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-06-20 16:38:21.589  INFO 19632 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 96ms. Found 5 repository interfaces.
2020-06-20 16:38:22.052  INFO 19632 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$fdb646fa] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-06-20 16:38:22.674  INFO 19632 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8443 (https)
2020-06-20 16:38:22.700  INFO 19632 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-06-20 16:38:22.700  INFO 19632 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.21]
2020-06-20 16:38:22.906  INFO 19632 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext


Behind the scene, Spring Boot is doing some work when starting the application on Tomcat. There are different 17 sources of properties that Spring Boot is using here. The official documentation Spring Boot provides the detail of these 17 sources. A developer can externalize these properties and many times we do that for application.properties. So if you have any of these properties configured, Spring Boot will read those properties instead of default sources.

Now if we expand the jar file spring-boot-autoconfigure-2.1.6.RELEASE.jar , you will see the number of directories under package org.springframework.boot.autoconfigure. All these sub packages are the beans that Spring Boot is pulling up, but only using them based on @Conditional annotation. Therefore, during startup Spring Boot will load some of these packages based on the dependencies you have configured in your Maven or Gradle build file.

From this jar, if we open the source file for ThymeleafAutoConfiguration, we will see the following:


/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.thymeleaf;

import java.util.Collection;
import java.util.LinkedHashMap;

import javax.annotation.PostConstruct;
import javax.servlet.DispatcherType;

import com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect;
import nz.net.ultraq.thymeleaf.LayoutDialect;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties.Reactive;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.util.MimeType;
import org.springframework.util.unit.DataSize;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Thymeleaf.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @author Eddú Meléndez
 * @author Daniel Fernández
 * @author Kazuki Shimizu
 * @author Artsiom Yudovin
 */
@Configuration
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {

 @Configuration
 @ConditionalOnMissingBean(name = "defaultTemplateResolver")
 static class DefaultTemplateResolverConfiguration {

  private static final Log logger = LogFactory.getLog(DefaultTemplateResolverConfiguration.class);

  private final ThymeleafProperties properties;

  private final ApplicationContext applicationContext;

  DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
   this.properties = properties;
   this.applicationContext = applicationContext;
  }

  @PostConstruct
  public void checkTemplateLocationExists() {
   boolean checkTemplateLocation = this.properties.isCheckTemplateLocation();
   if (checkTemplateLocation) {
    TemplateLocation location = new TemplateLocation(this.properties.getPrefix());
    if (!location.exists(this.applicationContext)) {
     logger.warn("Cannot find template location: " + location + " (please add some templates or check "
       + "your Thymeleaf configuration)");
    }
   }
  }

  @Bean
  public SpringResourceTemplateResolver defaultTemplateResolver() {
   SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
   resolver.setApplicationContext(this.applicationContext);
   resolver.setPrefix(this.properties.getPrefix());
   resolver.setSuffix(this.properties.getSuffix());
   resolver.setTemplateMode(this.properties.getMode());
   if (this.properties.getEncoding() != null) {
    resolver.setCharacterEncoding(this.properties.getEncoding().name());
   }
   resolver.setCacheable(this.properties.isCache());
   Integer order = this.properties.getTemplateResolverOrder();
   if (order != null) {
    resolver.setOrder(order);
   }
   resolver.setCheckExistence(this.properties.isCheckTemplate());
   return resolver;
  }

 }

 @Configuration
 protected static class ThymeleafDefaultConfiguration {

  private final ThymeleafProperties properties;

  private final Collection templateResolvers;

  private final ObjectProvider dialects;

  public ThymeleafDefaultConfiguration(ThymeleafProperties properties,
    Collection templateResolvers, ObjectProvider dialectsProvider) {
   this.properties = properties;
   this.templateResolvers = templateResolvers;
   this.dialects = dialectsProvider;
  }

  @Bean
  @ConditionalOnMissingBean
  public SpringTemplateEngine templateEngine() {
   SpringTemplateEngine engine = new SpringTemplateEngine();
   engine.setEnableSpringELCompiler(this.properties.isEnableSpringElCompiler());
   engine.setRenderHiddenMarkersBeforeCheckboxes(this.properties.isRenderHiddenMarkersBeforeCheckboxes());
   this.templateResolvers.forEach(engine::addTemplateResolver);
   this.dialects.orderedStream().forEach(engine::addDialect);
   return engine;
  }

 }

 @Configuration
 @ConditionalOnWebApplication(type = Type.SERVLET)
 @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
 static class ThymeleafWebMvcConfiguration {

  @Bean
  @ConditionalOnEnabledResourceChain
  @ConditionalOnMissingFilterBean(ResourceUrlEncodingFilter.class)
  public FilterRegistrationBean resourceUrlEncodingFilter() {
   FilterRegistrationBean registration = new FilterRegistrationBean<>(
     new ResourceUrlEncodingFilter());
   registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
   return registration;
  }

  @Configuration
  static class ThymeleafViewResolverConfiguration {

   private final ThymeleafProperties properties;

   private final SpringTemplateEngine templateEngine;

   ThymeleafViewResolverConfiguration(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
    this.properties = properties;
    this.templateEngine = templateEngine;
   }

   @Bean
   @ConditionalOnMissingBean(name = "thymeleafViewResolver")
   public ThymeleafViewResolver thymeleafViewResolver() {
    ThymeleafViewResolver resolver = new ThymeleafViewResolver();
    resolver.setTemplateEngine(this.templateEngine);
    resolver.setCharacterEncoding(this.properties.getEncoding().name());
    resolver.setContentType(
      appendCharset(this.properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
    resolver.setProducePartialOutputWhileProcessing(
      this.properties.getServlet().isProducePartialOutputWhileProcessing());
    resolver.setExcludedViewNames(this.properties.getExcludedViewNames());
    resolver.setViewNames(this.properties.getViewNames());
    // This resolver acts as a fallback resolver (e.g. like a
    // InternalResourceViewResolver) so it needs to have low precedence
    resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
    resolver.setCache(this.properties.isCache());
    return resolver;
   }

   private String appendCharset(MimeType type, String charset) {
    if (type.getCharset() != null) {
     return type.toString();
    }
    LinkedHashMap parameters = new LinkedHashMap<>();
    parameters.put("charset", charset);
    parameters.putAll(type.getParameters());
    return new MimeType(type, parameters).toString();
   }

  }

 }

 @Configuration
 @ConditionalOnWebApplication(type = Type.REACTIVE)
 @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
 static class ThymeleafReactiveConfiguration {

  private final ThymeleafProperties properties;

  private final Collection templateResolvers;

  private final ObjectProvider dialects;

  ThymeleafReactiveConfiguration(ThymeleafProperties properties, Collection templateResolvers,
    ObjectProvider dialectsProvider) {
   this.properties = properties;
   this.templateResolvers = templateResolvers;
   this.dialects = dialectsProvider;
  }

  @Bean
  @ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
  public SpringWebFluxTemplateEngine templateEngine() {
   SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
   engine.setEnableSpringELCompiler(this.properties.isEnableSpringElCompiler());
   engine.setRenderHiddenMarkersBeforeCheckboxes(this.properties.isRenderHiddenMarkersBeforeCheckboxes());
   this.templateResolvers.forEach(engine::addTemplateResolver);
   this.dialects.orderedStream().forEach(engine::addDialect);
   return engine;
  }

 }

 @Configuration
 @ConditionalOnWebApplication(type = Type.REACTIVE)
 @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
 static class ThymeleafWebFluxConfiguration {

  private final ThymeleafProperties properties;

  ThymeleafWebFluxConfiguration(ThymeleafProperties properties) {
   this.properties = properties;
  }

  @Bean
  @ConditionalOnMissingBean(name = "thymeleafReactiveViewResolver")
  public ThymeleafReactiveViewResolver thymeleafViewResolver(ISpringWebFluxTemplateEngine templateEngine) {
   ThymeleafReactiveViewResolver resolver = new ThymeleafReactiveViewResolver();
   resolver.setTemplateEngine(templateEngine);
   mapProperties(this.properties, resolver);
   mapReactiveProperties(this.properties.getReactive(), resolver);
   // This resolver acts as a fallback resolver (e.g. like a
   // InternalResourceViewResolver) so it needs to have low precedence
   resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
   return resolver;
  }

  private void mapProperties(ThymeleafProperties properties, ThymeleafReactiveViewResolver resolver) {
   PropertyMapper map = PropertyMapper.get();
   map.from(properties::getEncoding).to(resolver::setDefaultCharset);
   resolver.setExcludedViewNames(properties.getExcludedViewNames());
   resolver.setViewNames(properties.getViewNames());
  }

  private void mapReactiveProperties(Reactive properties, ThymeleafReactiveViewResolver resolver) {
   PropertyMapper map = PropertyMapper.get();
   map.from(properties::getMediaTypes).whenNonNull().to(resolver::setSupportedMediaTypes);
   map.from(properties::getMaxChunkSize).asInt(DataSize::toBytes).when((size) -> size > 0)
     .to(resolver::setResponseMaxChunkSizeBytes);
   map.from(properties::getFullModeViewNames).to(resolver::setFullModeViewNames);
   map.from(properties::getChunkedModeViewNames).to(resolver::setChunkedModeViewNames);
  }

 }

 @Configuration
 @ConditionalOnClass(LayoutDialect.class)
 protected static class ThymeleafWebLayoutConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public LayoutDialect layoutDialect() {
   return new LayoutDialect();
  }

 }

 @Configuration
 @ConditionalOnClass(DataAttributeDialect.class)
 protected static class DataAttributeDialectConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public DataAttributeDialect dialect() {
   return new DataAttributeDialect();
  }

 }

 @Configuration
 @ConditionalOnClass({ SpringSecurityDialect.class })
 protected static class ThymeleafSecurityDialectConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public SpringSecurityDialect securityDialect() {
   return new SpringSecurityDialect();
  }

 }

 @Configuration
 @ConditionalOnClass(Java8TimeDialect.class)
 protected static class ThymeleafJava8TimeDialect {

  @Bean
  @ConditionalOnMissingBean
  public Java8TimeDialect java8TimeDialect() {
   return new Java8TimeDialect();
  }

 }

}

If you are building a web application, thymeleaf template will be your default template engine.  Spring boot will load this class if TemplateMode and SpringBootEngine are loaded. We can see the use of @Conditional annotation.

How to exclude Spring Boot AutoConfiguration?

Spring Boot does offer an option to exclude any of the autoconfiguration you don’t want to include in your project.


@SpringBootApplication(exclude = {BatchAutoConfiguration.class)
public class HomeApplication 
{
    public static void main(String[] args) {
        SpringApplication.run(HomeApplication.class, args);
    }
}

One thing to remember here is that you must know why you are excluding a certain bean and if you are ok that it might exclude some dependent configurations.

Conclusion

In this post, I showed

  • How Spring Boot works and how you can build the Spring Boot application with some of the dependencies.
  • What Auto Configuration is and what it includes.

If you enjoyed this post or have any other questions, subscribe to my blog.

References

  1. Spring Boot Guide
  2. Spring Boot Official Documentation
  3. What is Spring Boot?