Auto Configuration

jim stafford

Introduction

Configuration focus thus far has been

  • under fairly static conditions

  • applied directly to a single application

Realistic Configuration Requirement

  • dynamically determined

  • modularized and not repeated

Dynamically Determined

  • dynamic based on runtime environment at startup

    • libraries present

    • properties defined

    • resources found

    • etc.

  • Examples:

    • what database will be used when in development, integration, or production?

    • what security should be enabled in development versus production areas?

Modularized and not Repeated

  • applications will ideally be broken into separate components

  • making components reusable for multiple applications is good practice

    • requires physically breaking them into separate modules

  • leaves us with repeated responsibility to configure reused components

  • there could be dozens of choices to make within a component configuration

    • application can be significantly simplified by an opinionated configuration supplied based on runtime environment

Forces At Work

If you find yourself

  • needing configurations determined dynamically at runtime

  • solving a repeated problem and bundling that into a library shared by multiple applications

Master the concepts behind Spring Boot’s Auto-configuration capability

Goals

The student will learn to:

  • Enable/disable bean creation based on condition(s) at startup

  • Create Auto-configuration/Starter module(s) that establish necessary dependencies and conditionally supplies beans

  • Resolve conflicts between alternate configurations

  • Locate environment and condition details to debug Auto-configuration issues

Objectives

At the conclusion of this lecture and related exercises, the student will be able to:

  1. Enable a @Component, @Configuration class, or @Bean factory method based on the result of a condition at startup

  2. Create Spring Boot Auto-configuration/Starter module(s)

  3. Bootstrap Auto-configuration classes into applications using a Spring Boot 3 org.springframework.boot.autoconfigure.AutoConfiguration.imports metadata file

  4. Create a conditional component based on the presence of a property value

  5. Create a conditional component based on a missing component

  6. Create a conditional component based on the presence of a class

  7. Define a processing dependency order for Auto-configuration classes

  8. Access textual debug information relative to conditions using the debug property

  9. Access web-based debug information relative to conditionals and properties using the Spring Boot Actuator

Review: Configuration Class

  • @Configuration classes used to bootstrap application using Java classes

    • modern alternative to legacy XML definitions that basically do the same thing — define and configure beans

  • can be the @SpringBootApplication class itself

    • appropriate for a small application

Configuration supplied within @SpringBootApplication Class
@SpringBootApplication
//==> wraps @EnableAutoConfiguration
//==> wraps @SpringBootConfiguration
//            ==> wraps @Configuration
public class SelfConfiguredApp {
    public static void main(String...args) {
        SpringApplication.run(SelfConfiguredApp.class, args);
    }

    @Bean
    public Hello hello() {
        return new StdOutHello("Application @Bean says Hey");
    }
}

Separate @Configuration Class

  • can be broken out into separate classes

    • appropriate for larger applications with distinct areas to be configured

@Configuration(proxyBeanMethods = false) (2)
public class AConfigurationClass {
    @Bean (1)
    public Hello hello() {
        return new StdOutHello("...");
    }
}
1bean scope defaults to "singleton"
2nothing directly calling the @Bean factory method; establishing a CGLIB proxy is unnecessary
@Configuration classes are commonly annotated with the proxyMethods=false attribute that tells Spring not to create extra proxy code to enforce normal, singleton return of the created instance to be shared by all callers since @Configuration class instances are only called by Spring. The javadoc for the annotation attribute describes the extra and unnecessary work saved.

Conditional Configuration

  • can make configurations dependent on conditions found at startup

    • individual @Bean factory methods (or the @Component annotated class)

    • entire @Configuration classes

Property Condition Example
...
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class StarterConfiguredApp {
    public static void main(String...args) {
        SpringApplication.run(StarterConfiguredApp.class, args);
    }

    @Bean
    @ConditionalOnProperty(prefix="hello", name="quiet", havingValue="true") (1)
    public Hello quietHello() {
        return new StdOutHello("(hello.quiet property condition set, Application @Bean says hi)");
    }
}
1 @ConditionalOnProperty annotation used to define a Hello bean based on the presence of the hello.quiet property having the value true

Property Value Condition Satisfied

Property Value Condition Satisfied Result
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --hello.quiet=true (1)
...
(hello.quiet property condition set, Application @Bean says hi) World (2)
1matching property supplied using command line
2satisfies property condition in @SpringBootApplication
The (parentheses) is trying to indicate a whisper. hello.quiet=true property turns on this behavior.

Property Value Condition Not Satisfied

Property Value Condition Not Satisfied
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar (1)
...
***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in info.ejava.examples.app.config.auto.AppCommand required a bean of type 'info.ejava.examples.app.hello.Hello' that could not be found. (2)


Action:

Consider defining a bean of type 'info.ejava.examples.app.hello.Hello' in your configuration.
1property either not specified or not specified with targeted value
2property condition within @SpringBootApplication not satisfied

Two Primary Configuration Phases

Configuration processing within Spring Boot is separated into two primary phases:

  1. User-defined configuration classes

    • processed first

    • part of the application module

    • located through the use of a @ComponentScan (wrapped by @SpringBootApplication)

    • establish the base configuration for the application

    • fill in any fine-tuning details.

  2. Auto-configuration classes

    • parsed second

    • outside the scope of the @ComponentScan

    • placed in separate modules, identified by metadata within those modules

    • enabled by application using @EnableAutoConfiguration (also wrapped by @SpringBootApplication)

    • provide defaults to fill in the reusable parts of the application

    • use User-defined configuration for details

Auto-Configuration

  • technically no different from any other @Configuration class except

    • inspected after User-defined @Configuration class(es) processing complete

    • based on being named in a descriptor file within META-INF/

  • alternate identification and second pass processing allows the core application to

    • make key directional and detailed decisions

    • control conditions for the Auto-configuration class(es)

Example Auto-Configuration Class
package info.ejava.examples.app.hello; (2)
...

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
    @Bean (1)
    public Hello hello(HelloProperties helloProperties) {
        return new StdOutHello(helloProperties.getGreeting());
    }
}
1Example Auto-configuration class provides unconditional @Bean factory for Hello
2this @Configuration package is outside the default scanning scope of @SpringBootApplication

Auto-Configuration Java Packages

Auto-Configuration Packages are Separate from Application

Auto-Configuration classes are designed to be outside the scope of the @SpringBootApplication package scanning. Otherwise, it would end up being a normal @Configuration class and processed within the main application JAR pre-processing.

package info.ejava.examples.app.config.auto;
@SpringBootApplication
package info.ejava.examples.app.hello; (1)

@Configuration(proxyBeanMethods = false)
public class HelloAutoConfiguration {
1app.hello is not under app.config.auto

@AutoConfiguration Annotation

Spring Boot 2.7 added the @AutoConfiguration annotation, which

  • extends @Configuration

  • permanently sets proxyBeanMethods to false

  • contains aliases for before/after configuration processing order

import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.AutoConfiguration;

//@Configuration(proxyBeanMethods = false)
@AutoConfiguration
public class HelloAutoConfiguration {


It helps document the purpose of the @Configuration class and provides some ease of use features, but the @Configuration annotation can still be used.

Supporting @ConfigurationProperties

  • this particular @Bean factory defines the @ConfigurationProperties class to encapsulate the details of configuring Hello

  • supplies a default greeting

    • making it optional for the User-defined configuration to do anything

Example Auto-Configuration Properties Class
@ConfigurationProperties("hello")
@Data
@Validated
public class HelloProperties {
    @NotBlank
    private String greeting = "HelloProperties default greeting says Hola!"; (1)
}
1Value used if user-configuration does not specify a property value

Locating Auto Configuration Classes

  • registered within META-INF/spring/ org.springframework.boot.autoconfigure.AutoConfiguration file of Auto-configuration class’s JAR

Auto-configuration Module JAR
$ jar tf target/hello-starter-*-SNAPSHOT.jar | egrep -v '/$|maven|MANIFEST.MF'

META-INF/spring-configuration-metadata.json (2)
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (1)
info/ejava/examples/app/hello/HelloProperties.class
info/ejava/examples/app/hello/HelloAutoConfiguration.class
1"auto-configuration" dependency JAR supplies …​ AutoConfiguration.imports
2@ConfigurationProperties class metadata generated by maven plugin for use by IDEs


It is common best-practice to host Auto-configuration classes in a separate module than the beans it configures. The Hello interface and Hello implementation(s) comply with this convention and are housed in separate modules.

META-INF Auto-configuration Metadata File

  • List each fully qualified class name in …​ AutoConfiguration.imports file, one per line

Spring Boot 3 AutoConfiguration Metadata File
# src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

info.ejava.examples.app.hello.HelloAutoConfiguration (1)
info.ejava.examples.app.hello.HelloResourceAutoConfiguration (2)
1Auto-configuration class registration
2this class is part of a later example; multiple classes are listed one-per-line

Spring Boot 2 META-INF/spring.factories

Prior to Spring Boot 2.7, the general purpose META-INF/spring.factories file was used to bootstrap auto-configuration classes. This approach was deprecated in 2.7 and eliminated in Spring Boot 3. If you are ever working with a legacy version of Spring Boot, you will have to use this approach.

  • list each class' fully qualified name, separated by commas, in spring.factories file

  • used property name equaling fully qualified classname of @EnableAutoConfiguration annotation

Spring Boot 2 Auto-Configuration Metadata Entry
# src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  info.ejava.examples.app.hello.HelloAutoConfiguration, \ (1)
  info.ejava.examples.app.hello.HelloResourceAutoConfiguration
1Auto-configuration class registration
The last line of the property cannot end with a comma or Spring Boot 2 will interpret entry as an empty class name

Example Auto-Configuration Module Source Tree

Example Auto-Configuration Module Structure
pom.xml
src
`-- main
    |-- java
    |   `-- info
    |       `-- ejava
    |           `-- examples
    |               `-- app
    |                   `-- hello
    |                       |-- HelloAutoConfiguration.java
    |                       `-- HelloProperties.java
    `-- resources
        `-- META-INF
            `-- spring
                `-- org.springframework.boot.autoconfigure.AutoConfiguration.imports

Auto-Configuration / Starter Roles/Relationships

Modules designed as starters can have varying designs with the following roles carried out:

  • Auto-configuration classes that conditionally wire the application.

  • An opinionated starter with dependencies that trigger the Auto-configuration rules

autoconfig modules

Example Starter Module pom.xml

The module commonly termed a starter and will have dependencies on

    <groupId>info.ejava.examples.app</groupId>
    <artifactId>hello-starter</artifactId>

    <dependencies>
        <dependency> (1)
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- commonly declares dependency on interface module -->
        <dependency> (2)
            <groupId>${project.groupId}</groupId>
            <artifactId>hello-service-api</artifactId>
            <version>${project.version}</version>
        </dependency> (2)
        <!-- hello implementation dependency -->
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>hello-service-stdout</artifactId>
            <version>${project.version}</version>
        </dependency>
1dependency on spring-boot-starter define classes pertinent to Auto-configuration
2starter modules commonly define dependencies on interface and implementation modules
  • remaining dependencies support the specific example module implementation

Application Starter Dependency

The application module declares dependency on the starter module containing or having a dependency on the Auto-configuration artifacts.

Application Module Dependency on Starter Module
<!-- takes care of initializing Hello Service for us to inject -->
<dependency>
    <groupId>${project.groupId}</groupId> (1)
    <artifactId>hello-starter</artifactId>
    <version>${project.version}</version> (1)
</dependency>
1For this example, the application and starter modules share the same groupId and version and leverage a ${project} variable to simplify the expression. That will likely not be the case with most starter module dependencies and will need to be spelled out.

Starter Brings in Pertinent Dependencies

  • starter dependency brings in

    • Hello Service interface

    • targeted implementation(s)

    • some implementation dependencies

Application Module Transitive Dependencies from Starter
$ mvn dependency:tree
...
[INFO] +- info.ejava.examples.app:hello-starter:jar:6.1.0-SNAPSHOT:compile
[INFO] |  +- info.ejava.examples.app:hello-service-api:jar:6.1.0-SNAPSHOT:compile
[INFO] |  +- info.ejava.examples.app:hello-service-stdout:jar:6.1.0-SNAPSHOT:compile
[INFO] |  +- org.projectlombok:lombok:jar:1.18.10:provided
[INFO] |  \- org.springframework.boot:spring-boot-starter-validation:jar:3.3.2:compile
...

Configured Application

  • example application contains component that requests the greeter implementation of type Hello to say hello to "World"

Injection Point for Auto-configuration Bean
import lombok.RequiredArgsConstructor;
...
@Component
@RequiredArgsConstructor (1)
public class AppCommand implements CommandLineRunner {
    private final Hello greeter; //<== component in App requires Hello injected

    public void run(String... args) throws Exception {
        greeter.sayHello("World");
    }
}
1lombok is being used to provide the constructor injection

Review: Unconditional Auto-Configuration Class

This starter dependency is bringing in a @Bean factory to construct an implementation of Hello, that can satisfy the injection dependency.

Example Auto-Configuration Class
package info.ejava.examples.app.hello;
...

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
    @Bean
    public Hello hello(HelloProperties helloProperties) { (1)
        return new StdOutHello(helloProperties.getGreeting());
    }
}
1Example Auto-configuration configured by HelloProperties

This bean will be unconditionally instantiated the way it is currently defined.

Review: Starter Module Default

  • starter dependency brings in an Auto-configuration class that instantiates a StdOutHello implementation configured by a HelloProperties class


Review: Auto-configuration class` Configuration Properties
@ConfigurationProperties("hello")
@Data
@Validated
public class HelloProperties {
    @NotBlank
    private String greeting = "HelloProperties default greeting says Hola!"; (1)
}
1hello.greeting default defined in @ConfigurationProperties class of starter/autoconfigure module

Produced Default Starter Greeting

Example Application Execution without Satisfying Property Condition
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
HelloProperties default greeting says Hola! World
  • HelloAutoConfiguration.hello @Bean instantiated with HelloProperties greeting of "HelloProperties default greeting says Hola!"

  • This Hello instance was injected into AppCommand, which added "World" to result

Example of Reasonable Default
This is an example of a component being Auto-configured with a reasonable default. It did not simply crash, demanding a greeting be supplied.

User-Application Supplies Property Details

  • application can supply properties to express details of greeting

application.properties
#appconfig-autoconfig-example application.properties
#uncomment to use this greeting
hello.greeting: application.properties Says - Hey


Runtime Output with hello.greeting Property Defined
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
application.properties Says - Hey World (1)
1auto-configured implementation using user-defined property
  • same scenario as before is occurring

  • except instantiation of the HelloProperties finds hello.greeting property to override Java default

Example of Configuring Details
This is an example of customizing the behavior of an Auto-configured component.

Auto-Configuration Conflict

Review: Conditional @Bean Factory

We saw how we could make a @Bean factory in the User-defined application module conditional (on the value of a property).

Conditional @Bean Factory
@SpringBootApplication
public class StarterConfiguredApp {
...
    @Bean
    @ConditionalOnProperty(prefix = "hello", name = "quiet", havingValue = "true")
    public Hello quietHello() {
        return new StdOutHello("(hello.quiet property condition set, Application @Bean says hi)");
    }
}

Potential Conflict

  • have condition where our two @Bean factory methods cause ambiguity error

    • @Bean factory in User-defined application module conditional (on property value)

    • @Bean factory in Auto-configuration class brought in by starter module

Example Output with Bean Factory Ambiguity
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --hello.quiet=true (1)
...
***************************
APPLICATION FAILED TO START
***************************
Description:

Parameter 0 of constructor in info.ejava.examples.app.config.auto.AppCommand
required a single bean, but 2 were found:
        - quietHello: defined by method 'quietHello' in info.ejava.examples.app.config.auto.StarterConfiguredApp
        - hello: defined by method 'hello' in class path resource [info/ejava/examples/app/hello/HelloAutoConfiguration.class]

This may be due to missing parameter name information

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
1Supplying the hello.quiet=true property value causes two @Bean factories to choose from

How Can Conflict Be Solved?

Conditional

Which Condition?

If User-definition did not supply a bean

@ConditionalOnMissingBean

@ConditionOnMissingBean Auto-Configuration Example
...
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean (1)
    public Hello hello(HelloProperties helloProperties) {
        return new StdOutHello(helloProperties.getGreeting());
    }
}
1@ConditionOnMissingBean causes Auto-configured @Bean method to be inactive when Hello bean already exists


  • @ConditionalOnMissingBean and sibling @ConditionalOnBean are special

    • meant to be used with Auto-configuration classes in the autoconfigure modules

  • Auto-configuration classes are processed after the User-defined classes

    • clear point to determine whether User-defined @Bean factory does or does not exist

  • any other use of these two annotations requires careful ordering and is not recommended

Bean Conditional Example Output

  • with @ConditionalOnMissingBean defined on Auto-configuration class and the property condition satisfied

    • we get bean injected from User-defined @Bean factory

Runtime with Property Condition Satisfied
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --hello.quiet=true
...
(hello.quiet property condition set, Application @Bean says hi) World
  • with property condition not satisfied, we get bean injected from Auto-configuration @Bean factory

Runtime with Property Condition Not Satisfied
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
application.properties Says - Hey World

Resource Conditional and Ordering

Example Condition on File Present and Evaluation Ordering
...
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;

@Configuration(proxyBeanMethods = false)
@ConditionalOnResource(resources = "file:./hello.properties") (1)
@AutoConfigureBefore(HelloAutoConfiguration.class) (2)
public class HelloResourceAutoConfiguration {
    @Bean
    public Hello resourceHello() {
        return new StdOutHello("hello.properties exists says hello");
    }
}
1Auto-configured class satisfied only when file hello.properties present
2This Auto-configuration class is processed prior to HelloAutoConfiguration

@AutoConfiguration Alternative

We can use the @AutoConfiguration annotation

  • wraps some of our desired settings



Example @AutoConfiguration Use
import org.springframework.boot.autoconfigure.AutoConfiguration;

@AutoConfiguration(before = HelloAutoConfiguration.class)
//==> wraps @Configuration(proxyBeanMethods = false)
//==> wraps @AutoConfigureBefore(HelloAutoConfiguration.class)
@ConditionalOnClass(StdOutHello.class)
@ConditionalOnResource(resources = "file:./hello.properties")
public class HelloResourceAutoConfiguration {

Registering Second Auto-Configuration Class

hello-starter AutoConfiguration.imports
# src/main/resources/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

info.ejava.examples.app.hello.HelloAutoConfiguration
info.ejava.examples.app.hello.HelloResourceAutoConfiguration

Resource Conditional Example Output

  • with hello.properties present

    • @Bean factory from HelloAutoConfiguration skipped — bean already exists

    • bean created using HelloResourceAutoConfiguration @Bean factory — evaluated before HelloAutoConfiguration

Resource Condition Satisfied
$ touch hello.properties

$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
hello.properties exists says hello World
  • when property file is not present:

    • @Bean factory from HelloAutoConfiguration used since neither property nor resource-based conditions satisfied

Resource Condition Not Satisfied
$ rm hello.properties
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
application.properties Says - Hey World

@Primary

  • familiar situation — ambiguous match

    • if we supply a hello.properties file and hello.quiet=true property value

Example Ambiguous Conditional Match
$ touch hello.properties
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --hello.quiet=true
...
***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in info.ejava.examples.app.config.auto.AppCommand required a single bean,
  but 2 were found:
        - quietHello: defined by method 'quietHello' in info.ejava.examples.app.config.auto.StarterConfiguredApp
        - resourceHello: defined by method 'resourceHello' in class path resource
      [info/ejava/examples/app/hello/HelloResourceAutoConfiguration.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans,
or using @Qualifier to identify the bean that should be consumed
  • this time — to correct — we add the @Primary annotation to our highest priority @Bean factory

    • if there is a conflict — this one will be used

...
import org.springframework.context.annotation.Primary;

@AutoConfiguration(before = HelloAutoConfiguration.class)
@ConditionalOnResource(resources = "file:./hello.properties")
public class HelloResourceAutoConfiguration {
    @Bean
    @Primary //chosen when there is a conflict
    public Hello resourceHello() {
        return new StdOutHello("hello.properties exists says hello");
    }
}

@Primary Example Output

  • avoided conflict error with one of the @Bean factories listed as @Primary

Ambiguous Choice Resolved through @Primary
$ touch hello.properties

$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --hello.quiet=true (1)
...
hello.properties exists says hello World
1@Primary condition satisfied overrides application @Bean condition

Class Conditions

  • important difference between conditions applied to @Configuration class or methods

    • class conditional annotations prevent entire class from loading when not satisfied

    • @Bean factory conditional annotations allow class to load but prevent method from being called when not satisfied

  • this works for missing classes too!

    • Spring Boot parses conditional class using ASM to detect and evaluate conditions before allowing class to be loaded into JVM

    • otherwise we would get ClassNotFoundException for the import of class we are trying to base condition on

Class Conditional Example

  • adding @ConditionalOnClass annotation to prevent @Configuration class from being loaded if implementation class does not exist on classpath

...
import info.ejava.examples.app.hello.stdout.StdOutHello; (2)
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(StdOutHello.class) (2)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Hello hello(HelloProperties helloProperties) {
        return new StdOutHello(helloProperties.getGreeting()); (1)
    }
}
1StdOutHello is the implementation instantiated by the @Bean factory method
2HelloAutoConfiguration.class will not get loaded if StdOutHello.class does not exist


  • @ConditionOnClass accepts either class or string expression of fully qualified classname

  • @ConditionalOnMissingClass accepts only string form of classname

Spring Boot Autoconfigure module contains many examples of real Auto-configuration classes

Excluding Auto Configurations

We can turn off certain Auto-configured classes using the

@SpringBootApplication(exclude = {})
// ==> wraps @EnableAutoConfiguration(exclude={})
public class StarterConfiguredApp {
    ...
}

Debugging Auto Configurations

  • many conditional User-defined and Auto-configurations going on

  • easy to get lost or make a mistake

  • two primary tools can expose details of conditional configuration decisions

    • Conditions Evaluation Report

    • Spring Boot Actuator

Conditions Evaluation Report

  • simplistic textual report of positive and negative condition evaluation matches

  • add a debug property to the configuration

    • --debug or -Ddebug to the command line

Conditions Evaluation Report Example

Conditions Evaluation Report Snippet
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --debug | less
...
============================
CONDITIONS EVALUATION REPORT
============================

Positive matches: (1)
-----------------
   HelloAutoConfiguration matched:
      - @ConditionalOnClass found required class 'info.ejava.examples.app.hello.stdout.StdOutHello' (OnClassCondition)

   HelloAutoConfiguration#hello matched:
      - @ConditionalOnMissingBean (types: info.ejava.examples.app.hello.Hello; SearchStrategy: all) did not find any beans (OnBeanCondition)

Negative matches: (2)
-----------------
   HelloResourceAutoConfiguration:
      Did not match:
         - @ConditionalOnResource did not find resource 'file:./hello.properties' (OnResourceCondition)
      Matched:
         - @ConditionalOnClass found required class 'info.ejava.examples.app.hello.stdout.StdOutHello' (OnClassCondition)

   StarterConfiguredApp#quietHello:
      Did not match:
         - @ConditionalOnProperty (hello.quiet=true) did not find property 'quiet' (OnPropertyCondition)
1Positive matches show which conditionals are activated and why
2Negative matches show which conditionals are not activated and why

Condition Evaluation Report Results

The report shows us that

  • HelloAutoConfiguration class was enabled because StdOutHello class was present

  • hello @Bean factory method of HelloAutoConfiguration class was enabled because no other beans were located

  • entire HelloResourceAutoConfiguration class was not loaded because file hello.properties was not present

  • quietHello @Bean factory method of application class was not activated because hello.quiet property was not found

Actuator Conditions

  • can also look at conditionals while Web applications are running using the Spring Boot Actuator

  • requires example transitioned from a command to a Web application

    • can be done technically by simply changing our starter in pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
<!--            <artifactId>spring-boot-starter</artifactId>-->
        </dependency>
  • also need to add a dependency on the spring-boot-starter-actuator module

        <!-- added to inspect env -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

Activating Actuator Conditions

  • Actuator, by default, will not expose any information without being configured to do so

  • can show a JSON version of the Conditions Evaluation Report by adding management.endpoints.web.exposure.include equal to value “conditions”

    • performed here using command line

    • could be in a profile-specific properties file appropriate for exposing this information

Enable Actuator Conditions Report to be Exposed
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar \
  --management.endpoints.web.exposure.include=conditions


Example Actuator Conditions Report
{
"contexts": {
  "application": {
    "positiveMatches": {
        "HelloAutoConfiguration": [{
            "condition": "OnClassCondition",
            "message": "@ConditionalOnClass found required class 'info.ejava.examples.app.hello.stdout.StdOutHello'"
            }],
        "HelloAutoConfiguration#hello": [{
            "condition": "OnBeanCondition",
            "message": "@ConditionalOnBean (types: info.ejava.examples.app.hello.Hello; SearchStrategy: all) did not find any beans"
            }],
...
,
    "negativeMatches": {
        "StarterConfiguredApp#quietHello": {
            "notMatched": [{
            "condition": "OnPropertyCondition",
            "message": "@ConditionalOnProperty (hello.quiet=true) did not find property 'quiet'"
            }],
            "matched": []
            },
        "HelloResourceAutoConfiguration": {
            "notMatched": [{
            "condition": "OnResourceCondition",
            "message": "@ConditionalOnResource did not find resource 'file:./hello.properties'"
            }],
            "matched": []
            },
...

Actuator Environment

  • also helpful to inspect the environment

    • determine value of properties

    • determine which source of properties is being used

  • add env to the exposure.include property to see information

Enable Actuator Conditions Report and Environment to be Exposed
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar \
   --management.endpoints.web.exposure.include=conditions,env

Actuator Environment Report

{
activeProfiles: [ ],
propertySources: [{
        name: "server.ports",
        properties: {
            local.server.port: {
                value: 8080
                }
            }
    },
    {
        name: "commandLineArgs",
        properties: {
            management.endpoints.web.exposure.include: {
                value: "conditions,env"
                }
            }
    },
...

Actuator Specific Property Source

Example Actuator Environment Report for Specific Property
{
  property: {
  source: "applicationConfig: [classpath:/application.properties]",
  value: "application.properties Says - Hey"
},
...

More Actuator

  • can explore some of the other Actuator endpoints by

    • changing the include property to *

    • revisiting the main actuator endpoint

  • Actuator Documentation is available on the web

Expose All Actuator Endpoints
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar \
   --management.endpoints.web.exposure.include="*" (1)
1double quotes ("") being used to escape * special character on command line

Summary

In this module we:

  • Defined conditions for @Configuration classes and @Bean factory methods that are evaluated at runtime startup

  • Placed User-defined conditions, which are evaluated first, in with application module

  • Placed Auto-configuration classes in separate starter module to automatically bootstrap applications with specific capabilities

  • Added conflict resolution and ordering to conditions to avoid ambiguous matches

  • Discovered how class conditions can help prevent entire @Configuration classes from being loaded and disrupt the application because an optional class is missing

  • Learned how to debug conditions and visualize the runtime environment through use of the debug property or by using the Actuator for web applications