Creating Your Own Auto-configuration for Spring Boot Applications.

Spring Boot has emerged as a game-changer in the ever-evolving landscape of software development, empowering developers to streamline application configuration and deployment processes. At the heart of this transformative framework lies the concept of auto-configuration, a feature that intelligently sets up your application based on the libraries in the classpath. This automated configuration process saves valuable time, ensures consistency, and reduces the potential for human error.

However, while Spring Boot’s built-in auto-configuration capabilities are extensive, scenarios may arise where you must tailor the configuration to meet your project’s unique requirements. This is where the ability to create your custom auto-configurations comes into play, allowing you to fine-tune the framework’s behavior and extend its functionality to align with your specific needs.

Grasping the Essence of Auto-Configured Beans

Before delving into the intricacies of crafting your auto-configurations, it’s crucial to understand the underlying mechanics of how Spring Boot handles auto-configured beans. The core of this process revolves around classes annotated with @AutoConfiguration, which are essentially standard @Configuration classes adorned with additional @Conditional annotations.

These conditional annotations are pivotal in determining when the auto-configuration should be applied. Commonly used annotations include @ConditionalOnClass and @ConditionalOnMissingBean, which checks for the presence or absence of specific classes or beans within the application context. By leveraging these annotations judiciously, you can ensure that your auto-configuration is activated only under the desired circumstances.

Unveiling the Auto-Configuration Candidate Discovery Process

Spring Boot recognizes and incorporates your custom auto-configurations following a specific discovery process. The framework scans for a

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

file within your project’s packaged JAR. This file serves as a manifest, listing the fully qualified names of your auto-configuration classes, one per line.

It’s important to note that auto-configurations must be explicitly listed in this import file to be considered valid candidates. Attempting to rely on component scanning or other means of auto-discovery will not work, as Spring Boot strictly adheres to this prescribed method for identifying auto-configuration classes.

Establishing Order and Precedence

In specific scenarios, the order in which auto-configurations are applied can significantly impact the overall behavior of your application. To address this concern, Spring Boot provides several mechanisms to control the precedence of your custom auto-configurations.

The @AutoConfiguration the annotation itself offers attributes like beforebeforeNameafter, and afterName, which allows you to specify the order in which your auto-configuration should be applied relative to other configurations. Additionally, the @AutoConfigureBefore and @AutoConfigureAfter annotations provide a dedicated means of enforcing order constraints.

For situations where direct knowledge of other auto-configurations is unnecessary, you can leverage the @AutoConfigureOrder annotation. This annotation functions similarly to the standard @Order annotation but is specifically tailored for ordering auto-configuration classes.

Conditional Annotations: Tailoring Auto-Configuration Behavior

One of the most potent aspects of creating custom auto-configurations is leveraging Spring Boot’s rich set of conditional annotations. These annotations enable you to fine-tune the circumstances under which your auto-configuration should be applied, ensuring that it remains relevant and practical within the context of your application.

Class Conditions

The @ConditionalOnClass and @ConditionalOnMissingClass Annotations allow you to include or exclude your auto-configuration based on the presence or absence of specific classes in the classpath. This can be particularly useful when your configuration relies on third-party libraries or requires support for different runtime environments.

Bean Conditions

Similarly, the @ConditionalOnBean and @ConditionalOnMissingBean Annotations provide a means to control the application of your auto-configuration based on the existence or absence of specific beans within the application context. This feature can be invaluable when your configuration needs to interact with or extend existing beans or when you need to provide fallback configurations in the absence of specific beans.

Property Conditions

The @ConditionalOnProperty An annotation enables you to tie your auto-configuration behavior to the values of specific properties within the Spring Environment. This annotation allows you to define conditions based on the presence, absence, or particular values of properties, providing a flexible way to configure your application’s behavior according to external configuration sources.

Resource Conditions

In scenarios where your auto-configuration relies on the availability of specific resources, such as configuration files or external data sources, the @ConditionalOnResource annotation can be a powerful ally. By specifying the required resources, you can ensure that your auto-configuration is applied only when those resources are present, preventing potential runtime errors or unexpected behavior.

Web Application Conditions

For applications that operate within a web context, Spring Boot provides the @ConditionalOnWebApplication and @ConditionalOnNotWebApplication annotations. These annotations allow you to tailor your auto-configuration based on whether the application is a web application, ensuring that your configurations remain relevant and practical within the appropriate runtime environment.

SpEL Expression Conditions

While Spring Boot offers a comprehensive set of predefined conditional annotations, there may be instances where you need to define more complex or custom conditions. In such cases, the @ConditionalOnExpression annotation comes to the rescue, allowing you to specify conditions using Spring Expression Language (SpEL) expressions. This powerful feature enables you to craft intricate conditions based on various factors, such as system properties, environment variables, or even the state of your application’s beans.

Validating and Testing Your Auto-Configuration

As with any software component, thorough testing is crucial to ensure the reliability and correctness of your custom auto-configurations. Spring Boot provides a dedicated testing utility called ApplicationContextRunner to facilitate this process.

The ApplicationContextRunner allows you to create well-defined ApplicationContext instances representing specific combinations of user configurations, condition evaluations, and other factors that may influence your auto-configuration behavior. By simulating various scenarios and asserting the expected outcomes, you can gain confidence in the robustness and accuracy of your auto-configurations.

Disabling Specific Auto-Configuration Classes

Despite the flexibility and power of auto-configurations, situations may arise where you need to disable specific auto-configuration classes altogether. Spring Boot offers several mechanisms to achieve this, ensuring that you have complete control over the framework’s behavior.

The exclude and excludeName attributes of the @EnableAutoConfiguration or @SpringBootApplication Annotations allow you to explicitly exclude specific auto-configuration classes from being applied. Alternatively, you can leverage the spring.autoconfigure.exclude property to define a list of auto-configuration classes excluded globally.

Crafting Your Starter: Bundling Auto-Configurations

While creating custom auto-configurations is a powerful capability, Spring Boot takes this concept further by introducing the notion of “starters.” A starter is a packaged bundle combining your auto-configuration code with the typical libraries and dependencies required for a specific functionality or feature.

By creating your starter, you can provide an opinionated and streamlined way for other developers to incorporate your auto-configurations into their projects. This approach simplifies the integration process, ensures consistency, and reduces the potential for configuration errors.

When crafting your starter, it’s recommended to follow a modular structure, separating the auto-configuration code into a autoconfigure module and bundling the necessary dependencies and libraries into a separate starter module. This separation promotes code organization, reusability, and maintainability while allowing others to customize or extend your starter based on their needs.

Embracing Auto-Configuration Packages

In addition to creating custom auto-configurations and starters, Spring Boot provides a mechanism to define auto-configuration packages. These packages are default scanning locations for auto-configured features, such as entities and Spring Data repositories.

The @EnableAutoConfiguration annotation (directly or through its presence @SpringBootApplication) determines the default auto-configuration package. However, you can extend this default behavior by using the @AutoConfigurationPackage annotation to specify additional packages that should be scanned for auto-configured components.

Example for Auto-configuration

Structure developers Auto-configuration Module

/src/main/java/com/example/autoconfig/
    ├── MyAutoConfiguration.java
/src/main/java/com/example/service/
    ├── MyService.java
/src/main/java/com/example/
    ├── MyApplication.java

Define a Configuration Class

package com.example.autoconfig;

import com.example.autoconfig.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@Configuration
public class MyAutoConfiguration {

    @Bean
    public MyService myService() {
        return new MyService();
    }
}

Define a Service Class

@Setter
@Getter
@Slf4j
public class MyService {
    private String message;

    public MyService() {
        this.message = "Default message";
    }

    public MyService(String message) {
        this.message = message;
    }

    public void performTask() {
        log.info("Performing task with message: {}", message);
    }
}

Test the Auto-configuration

import com.example.autoconfig.service.MyService;;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyApplication {

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

    @Bean
    public CommandLineRunner runner(MyService myService) {
        return args -> myService.performTask();
    }
}

Test result

2024-10-11T15:16:19.438+07:00  INFO 5880 --- [auto-configuration] [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 956 ms
2024-10-11T15:16:19.540+07:00  INFO 5880 --- [auto-configuration] [  restartedMain] c.e.a.config.MyAutoConfiguration         : MyAutoConfiguration -> myService
2024-10-11T15:16:19.835+07:00  INFO 5880 --- [auto-configuration] [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2024-10-11T15:16:19.843+07:00  INFO 5880 --- [auto-configuration] [  restartedMain] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2024-10-11T15:16:19.906+07:00  INFO 5880 --- [auto-configuration] [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2024-10-11T15:16:19.916+07:00  INFO 5880 --- [auto-configuration] [  restartedMain] c.e.auto_configuration.MyApplication     : Started MyApplication in 1.779 seconds (process running for 2.151)
2024-10-11T15:16:19.977+07:00  INFO 5880 --- [auto-configuration] [  restartedMain] c.e.a.service.MyService                  : Performing task with message: Default message

Example Conditional Auto-configuration

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class MyConditionalAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        log.info("MyAutoConfiguration -> myService");
        return new MyService();
    }
}

Testing Conditional Auto-configuration

@SpringBootApplication
@Slf4j
public class MyApplication {

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

    @Bean
    public CommandLineRunner runner(MyService myService) {
        return args -> myService.performTask();
    }
}

Test result

2024-10-11T15:02:47.303+07:00  INFO 3484 --- [auto-configuration] [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 960 ms
2024-10-11T15:02:47.411+07:00  INFO 3484 --- [auto-configuration] [  restartedMain] c.e.a.config.MyAutoConfiguration         : MyAutoConfiguration -> myService
2024-10-11T15:02:47.702+07:00  INFO 3484 --- [auto-configuration] [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2024-10-11T15:02:47.710+07:00  INFO 3484 --- [auto-configuration] [  restartedMain] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2024-10-11T15:02:47.759+07:00  INFO 3484 --- [auto-configuration] [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2024-10-11T15:02:47.768+07:00  INFO 3484 --- [auto-configuration] [  restartedMain] c.e.auto_configuration.MyApplication     : Started MyApplication in 1.754 seconds (process running for 2.164)
2024-10-11T15:02:47.824+07:00  INFO 3484 --- [auto-configuration] [  restartedMain] c.e.a.service.MyService                  : Performing task with message: Default message

Defining the Bean Explicitly

If developers define MyService Explicitly, the auto-configuration will be skipped.

@Configuration
@Slf4j
public class MyAutoConfiguration {

   @Bean
    public MyService myService() {
        log.info("MyAutoConfiguration -> myService");
        return new MyService("Test") {
            @Override
            public void performTask() {
                log.info("Custom service is running! and {}" , getMessage()) ;
            }
        };
    }
}

Testing Bean Explicitly in Auto-configuration

@SpringBootApplication
@Slf4j
public class MyApplication {

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

    @Bean
    public CommandLineRunner runner(MyService myService) {
        return args -> myService.performTask();
    }
}

Test result

2024-10-11T15:11:53.683+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-10-11T15:11:53.684+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 985 ms
2024-10-11T15:11:53.790+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] c.e.a.config.MyAutoConfiguration         : MyAutoConfiguration -> myService
2024-10-11T15:11:54.092+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2024-10-11T15:11:54.099+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2024-10-11T15:11:54.158+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2024-10-11T15:11:54.166+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] c.e.auto_configuration.MyApplication     : Started MyApplication in 1.806 seconds (process running for 2.227)
2024-10-11T15:11:54.224+07:00  INFO 98840 --- [auto-configuration] [  restartedMain] c.e.a.config.MyAutoConfiguration         : Custom service is running! and Test

Conclusion

In the ever-evolving world of software development, where complexity and scalability are paramount, Spring Boot’s auto-configuration capabilities have revolutionized how applications are configured and deployed. By empowering developers to create tailored auto-configurations, Spring Boot unlocks a world of possibilities and fosters a culture of innovation and customization.

Whether you’re working on a shared library, an open-source project, or a commercial application, the ability to craft custom auto-configurations allows you to fine-tune the framework’s behavior, extend its functionality, and ensure that your application meets the unique requirements of your project.

As you craft your auto-configurations, remember to leverage the power of conditional annotations, utilize the testing utilities provided by Spring Boot, and explore the possibilities of creating your starters. With these tools, you can unlock new levels of efficiency, consistency, and scalability, ultimately paving the way for more robust and reliable software solutions.