1. Introduction
Thus far we have focused on how to configure an application within the primary application module, under fairly static conditions, and applied directly to a single application.
However, our application configuration will likely be required to be:
-
dynamically determined - Application configurations commonly need to be dynamic based on libraries present, properties defined, resources found, etc. at startup. For example, 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 - Breaking the application down into separate components and making these components reusable in multiple applications by physically breaking them into separate modules is a good practice. However, that leaves us with the repeated responsibility to configure the components reused. Many times there could be dozens of choices to make within a component configuration, and the application can be significantly simplified if an opinionated configuration can be supplied based on the runtime environment of the module.
If you find yourself needing configurations determined dynamically at runtime or find yourself solving a repeated problem and bundling that into a library shared by multiple applications — then you are going to want to master the concepts within Spring Boot’s Auto-configuration capability. Some of these Auto-configuration capabilities mentioned can be placed directly into the application while others are meant to be placed into separate Auto-configuration modules called "starter" modules. "Starter" modules can come with an opinionated, default way to configure the component for use with as little work as possible.
1.1. 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
1.2. Objectives
At the conclusion of this lecture and related exercises, the student will be able to:
-
Enable a
@Component,@Configurationclass, or@Beanfactory method based on the result of a condition at startup -
Create Spring Boot Auto-configuration/Starter module(s)
-
Bootstrap Auto-configuration classes into applications using a Spring Boot 3
org.springframework.boot.autoconfigure.AutoConfiguration.importsmetadata file -
Create a conditional component based on the presence of a property value
-
Create a conditional component based on a missing component
-
Create a conditional component based on the presence of a class
-
Define a processing dependency order for Auto-configuration classes
-
Access textual debug information relative to conditions using the
debugproperty -
Access web-based debug information relative to conditionals and properties using the Spring Boot Actuator
2. Injection/Inversion of Control
We define dependencies between components using interfaces.
public interface Hello {
void sayHello(String name);
}
The container injects the implementation.
@Component
@RequiredArgsConstructor
public class AppCommand implements CommandLineRunner {
private final Hello greeter;
public void run(String... args) throws Exception {
greeter.sayHello("World");
}
}
But how is the container configured with an implementation?
3. Review: Configuration Class
As we have seen earlier, @Configuration classes are how we bootstrap an application
using Java classes. They are the modern alternative to the legacy XML definitions that
basically do the same thing — define and configure beans.
@Configuration classes can be the @SpringBootApplication class itself. This would be
appropriate for a small application.
@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");
}
}
3.1. Review: Separate @Configuration Class
@Configuration classes can be broken out into separate classes. This would be
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("...");
}
}
| 1 | bean scope defaults to "singleton" |
| 2 | nothing 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.
|
4. Conditional Configuration
We can make @Bean factory methods (or the @Component annotated class) and entire @Configuration classes dependent on conditions found at startup.
The following example uses the @ConditionalOnBooleanProperty annotation (added in 3.5.0) to define a Hello bean based on the presence of the hello.quiet property having the boolean value true.
Prior to that, @ConditionalOnProperty annotation could have been used to judge whether hello.quiet property had the String value "true".
...
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
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") (2)
//since 3.5.0
@ConditionalOnBooleanProperty(prefix="hello", name="quiet", havingValue=true) (1)
public Hello quietHello() {
return new StdOutHello("(hello.quiet property condition set, Application @Bean says hi)");
}
}
| 1 | @ConditionOnBooleanProperty (since 3.5.0) annotation used to define a Hello bean based on the presence of hello.quiet property having the boolean value of true |
| 2 | @ConditionalOnProperty annotation used to evaluate property having the String value "true" |
4.1. Property Value Condition Satisfied
The following is an example of the property being defined with the targeted value.
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --hello.quiet=true (1)
...
(hello.quiet property condition set, Application @Bean says hi) World (2)
| 1 | matching property supplied using command line |
| 2 | satisfies property condition in @SpringBootApplication |
The (parentheses) is trying to indicate a whisper.
hello.quiet=true property turns on this behavior.
|
4.2. Property Value Condition Not Satisfied
The following is an example of when the property is missing.
Since there is no Hello bean factory, we encounter an error that we will look to solve using a separate Auto-configuration module.
$ 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.
| 1 | property either not specified or not specified with targeted value |
| 2 | property condition within @SpringBootApplication not satisfied |
5. Two Primary Configuration Phases
Configuration processing within Spring Boot is separated into two primary phases:
-
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.
-
-
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
-
6. Auto-Configuration
An Auto-configuration class is technically no different from any other @Configuration class except that it is meant to be inspected after the user-defined @Configuration class(es) and based on being named in a descriptor file within META-INF/spring.
This alternate identification and second pass processing allows the core application to make key directional and detailed decisions and control conditions for the Auto-configuration class(es).
There are some tools provided to assist in defining Auto-configuration classes
-
@AutoConfigurationannotation replaces the@Configurationannotation to allow these classes to be filtered from the component scanpath -
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsdescriptor file to identify Auto-configuration classes -
AutoConfigurationExcludeFilterclass actively filters classes annotated with@AutoConfigurationout of the component scanpath that are not listed in theAutoConfiguration.importsdescriptor file
6.1. Example Auto-Configuration Class
The following Auto-configuration class example defines an unconditional Hello bean
factory configured using a @ConfigurationProperties class.
package info.ejava.examples.app.hello; (2)
...
@AutoConfiguration (3)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@Bean (1)
public Hello hello(HelloProperties helloProperties) {
return new StdOutHello(helloProperties.getGreeting());
}
}
| 1 | example Auto-configuration class provides unconditional @Bean factory for Hello |
| 2 | class is outside default component scanpath of @SpringBootApplication |
| 3 | armed with @AutoConfiguration annotation to exclude from component scanpath |
6.2. AutoConfigurationExcludeFilter
The @SpringBootApplication includes a default @ComponentScan that defines a set of filters to implement the Auto-configuration conventions.
AutoConfigurationExcludeFilter is an exclude filter and will filter out any class annotated with @AutoConfiguration that has not been included in the AutoConfiguration.imports.
@ComponentScan(excludeFilters = { (1)
@Filter(type = FilterType.CUSTOM, classes=TypeExcludeFilter.class), (3)
@Filter(type = FilterType.CUSTOM, classes=AutoConfigurationExcludeFilter.class) }) (2)
| 1 | default filters supplied by @SpringBootApplication |
| 2 | excludes @AutoConfiguration classes not mentioned in AutoConfiguration.imports |
| 3 | framework that mostly allows test slices to customize class exclusions |
6.3. Supporting @ConfigurationProperties
This particular @Bean factory defines the @ConfigurationProperties class to
encapsulate the details of configuring Hello.
@AutoConfiguration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
The @ConfigurationProperties class supplies a default greeting making it optional for the User-defined configuration to do anything.
@ConfigurationProperties("hello")
@Data
@Validated
public class HelloProperties {
@NotBlank
private String greeting = "HelloProperties default greeting says Hola!"; (1)
}
| 1 | Value used if user-configuration does not specify a property value |
6.4. Locating Auto Configuration Classes
A dependency JAR makes the Auto-configuration class(es) known to the application by supplying a descriptor file (META-INF/spring/ org.springframework.boot.autoconfigure.AutoConfiguration.imports) and listing the Auto-configuration classes within that file.
The example below shows the metadata file ("META-INF/…AutoConfiguration.imports") and an Auto-configuration class ("HelloAutoConfiguration") that will be named within that metadata file.
$ 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.
|
6.5. META-INF Auto-configuration Metadata File
Auto-configuration classes are registered in the … AutoConfiguration.imports file by listing the class' fully qualified name, one per line.
# 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)
| 1 | Auto-configuration class registration |
| 2 | this class is part of a later example; multiple classes are listed one-per-line |
6.6. 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.
Auto-configuration classes were registered using the property name equaling the fully qualified classname of the @EnableAutoConfiguration annotation and the value equaling the fully qualified classname of the Auto-configuration class(es).
Multiple classes can be specified separated by commas.
The last entry on a line cannot end with a comma.
# 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
| 1 | Auto-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 |
6.7. Example Auto-Configuration Module Source Tree
Our configuration and properties class — along with the org.springframework.boot.autoconfigure.AutoConfiguration.imports file get placed in a separate module source tree.
pom.xml
src
`-- main
|-- java
| `-- info
| `-- ejava
| `-- examples
| `-- app
| `-- hello
| |-- HelloAutoConfiguration.java
| `-- HelloProperties.java
`-- resources
`-- META-INF
`-- spring
`-- org.springframework.boot.autoconfigure.AutoConfiguration.imports
6.10. Application Starter Dependency
The application module declares a dependency on the starter module containing or having a dependency on the Auto-configuration artifacts.
<!-- 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>
| 1 | For 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. |
6.11. Starter Brings in Pertinent Dependencies
The starter dependency brings in the Hello Service interface, targeted implementation(s), and some implementation dependencies.
$ mvn dependency:tree
...
[INFO] +- info.ejava.examples.app:hello-starter:jar:6.1.1-SNAPSHOT:compile
[INFO] | +- info.ejava.examples.app:hello-service-api:jar:6.1.1-SNAPSHOT:compile
[INFO] | +- info.ejava.examples.app:hello-service-stdout:jar:6.1.1-SNAPSHOT:compile
[INFO] | +- org.projectlombok:lombok:jar:1.18.10:provided
[INFO] | \- org.springframework.boot:spring-boot-starter-validation:jar:3.5.5:compile
...
7. Configured Application
The example application contains a component that requests the greeter implementation to say hello to "World".
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");
}
}
| 1 | lombok is being used to provide the constructor injection |
7.1. 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.
package info.ejava.examples.app.hello;
...
@AutoConfiguration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@Bean
public Hello hello(HelloProperties helloProperties) { (1)
return new StdOutHello(helloProperties.getGreeting());
}
}
| 1 | Example Auto-configuration configured by HelloProperties |
This bean will be unconditionally instantiated the way it is currently defined.
7.2. Review: Starter Module Default
The starter dependency brings in an Auto-configuration class that instantiates a StdOutHello implementation configured by a HelloProperties class.
@ConfigurationProperties("hello")
@Data
@Validated
public class HelloProperties {
@NotBlank
private String greeting = "HelloProperties default greeting says Hola!"; (1)
}
| 1 | hello.greeting default defined in @ConfigurationProperties class of starter/autoconfigure module |
7.3. Produced Default Starter Greeting
This produces the default greeting
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
HelloProperties default greeting says Hola! World
The HelloAutoConfiguration.hello @Bean was instantiated with the HelloProperties greeting of "HelloProperties default greeting says Hola!".
This Hello instance was injected into the AppCommand, which added "World" to the 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.
|
7.4. User-Application Supplies Property Details
Since the Auto-configuration class is using a properties class, we can define properties (aka "the details") in the main application for the dependency module to use.
#appconfig-autoconfig-example application.properties
#uncomment to use this greeting
hello.greeting: application.properties Says - Hey
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
application.properties Says - Hey World (1)
| 1 | auto-configured implementation using user-defined property |
The same scenario as before is occurring except this time the instantiation of the HelloProperties finds a hello.greeting property to override the Java default.
|
Example of Configuring Details
This is an example of customizing the behavior of an Auto-configured component.
|
8. Auto-Configuration Conflict
8.1. 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).
@SpringBootApplication
public class StarterConfiguredApp {
...
@Bean
@ConditionalOnBooleanProperty(prefix = "hello", name = "quiet", havingValue = true)
public Hello quietHello() {
return new StdOutHello("(hello.quiet property condition set, Application @Bean says hi)");
}
}
8.2. Potential Conflict
We also saw how to define a @Bean factory in an Auto-configuration class brought in by starter module.
We now have a condition where the two can cause an ambiguity error that we need to account for.
$ 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
| 1 | Supplying the hello.quiet=true property value causes two @Bean factories to choose from |
8.3. @ConditionalOnMissingBean
One way to solve the ambiguity is by using the @ConditionalOnMissingBean annotation — which defines a condition based on the absence of a bean.
Most conditional annotations can be used in both the application and autoconfigure modules.
However, the @ConditionalOnMissingBean and its sibling @ConditionalOnBean are special and meant to be used with Auto-configuration classes in the autoconfigure modules.
Since the Auto-configuration classes are processed after the User-defined classes — there is a clear point to determine whether a User-defined bean does or does not exist. Any other use of these two annotations requires careful ordering and is not recommended.
...
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@AutoConfiguration
@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 |
8.4. Bean Conditional Example Output
With the @ConditionalOnMissingBean defined on the Auto-configuration class and the property
condition satisfied, we get the bean injected from the User-defined @Bean factory.
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar --hello.quiet=true
...
(hello.quiet property condition set, Application @Bean says hi) World
With the property condition not satisfied, we get the bean injected from the
Auto-configuration @Bean factory. Wahoo!
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
application.properties Says - Hey World
9. Resource Conditional and Ordering
We can also define a condition based on the presence of a resource on the filesystem
or classpath using the
@ConditionOnResource. The following example satisfies the condition if
the file hello.properties exists in the current directory. We are also
going to order our Auto-configured classes with the help of the
@AutoConfigureBefore annotation. There is a sibling
@AutoConfigureAfter annotation as well as a
AutoConfigureOrder we could have used.
...
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
@AutoConfiguration(before = HelloAutoConfiguration.class) //since boot 2.7.0 (2)
@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");
}
}
| 1 | Auto-configured class satisfied only when file hello.properties present |
| 2 | This Auto-configuration class is processed prior to HelloAutoConfiguration |
9.1. Registering Second Auto-Configuration Class
This second Auto-configuration class is being provided in the same, hello-starter module, so we need to update the '… AutoConfiguration.imports` file.
We do this by listing the second class within the same file.
# src/main/resources/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
info.ejava.examples.app.hello.HelloAutoConfiguration
info.ejava.examples.app.hello.HelloResourceAutoConfiguration
9.2. Resource Conditional Example Output
The following execution with hello.properties present in the current directory
satisfies the condition, causes the @Bean factory from HelloAutoConfiguration
to be skipped because the bean already exists.
$ touch hello.properties
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
hello.properties exists says hello World
-
when property file is not present:
-
@Beanfactory fromHelloAutoConfigurationused since neither property nor resource-based conditions satisfied
-
$ rm hello.properties
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar
...
application.properties Says - Hey World
11. Class Conditions
There are many conditions we can add to our @Configuration class or methods. However,
there is an important difference between the two.
-
class conditional annotations prevent the entire class from loading when not satisfied
-
@Beanfactory conditional annotations allow the class to load but prevent the method from being called when not satisfied
This works for missing classes too! Spring Boot parses the conditional class using ASM to detect and then evaluate conditions before allowing the class to be loaded into the JVM.
Otherwise, we would get a ClassNotFoundException for the import of a class we are trying to base our condition on.
11.1. Class Conditional Example
In the following example, I am adding @ConditionalOnClass annotation to prevent the class from being loaded if the implementation class does not exist on the 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)
}
}
| 1 | StdOutHello is the implementation instantiated by the @Bean factory method |
| 2 | HelloAutoConfiguration.class will not get loaded if StdOutHello.class does not exist |
The @ConditionOnClass accepts either a class or string expression of the fully qualified classname.
The sibling
@ConditionalOnMissingClass accepts only the string form of the classname.
12. Excluding Auto Configurations
We can turn off certain Auto-configured classes using the
-
excludeattribute of the@EnableAutoConfigurationannotation -
excludeattribute of the@SpringBootApplicationannotation which wraps the@EnableAutoConfigurationannotation
@SpringBootApplication(exclude = {})
// ==> wraps @EnableAutoConfiguration(exclude={})
public class StarterConfiguredApp {
...
}
13. Debugging Auto Configurations
With all these conditional User-defined and Auto-configurations going on, it is easy to get lost or make a mistake. There are two primary tools that can be used to expose the details of the conditional configuration decisions.
13.1. Conditions Evaluation Report
It is easy to get a simplistic textual report of positive and negative condition evaluation matches
by adding a debug property to the configuration. This can be done by adding --debug or -Ddebug
to the command line.
The following output shows only the positive and negative matching conditions relevant to our example. There is plenty more in the full output.
13.2. Conditions Evaluation Report Example
$ 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)
| 1 | Positive matches show which conditionals are activated and why |
| 2 | Negative matches show which conditionals are not activated and why |
13.3. Condition Evaluation Report Results
The report shows us that
-
HelloAutoConfigurationclass was enabled becauseStdOutHelloclass was present -
hello@Beanfactory method ofHelloAutoConfigurationclass was enabled because no other beans were located -
entire
HelloResourceAutoConfigurationclass was not loaded because filehello.propertieswas not present -
quietHello@Beanfactory method of application class was not activated becausehello.quietproperty was not found
13.4. Actuator Conditions
We can also get a look at the conditionals while the application is running for Web applications using the Spring Boot Actuator. However, doing so requires that we transition our application from a command to a Web application. Luckily, this can be done technically by simply changing our starter in the pom.xml file.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- <artifactId>spring-boot-starter</artifactId>-->
</dependency>
We 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>
13.5. Activating Actuator Conditions
The Actuator, by default, will not expose any information without being configured to do so.
We can show a JSON version of the Conditions Evaluation Report by adding the management.endpoints.web.exposure.include equal to the value conditions.
I will do that on the command line here. Normally it would be in a profile-specific properties file appropriate for exposing this information.
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar \
--management.endpoints.web.exposure.include=conditions
The Conditions Evaluation Report is available at the following URL: http://localhost:8080/actuator/conditions.
{
"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": []
},
...
13.6. Actuator Environment
It can also be helpful to inspect the environment to determine the value of properties and which source
of properties is being used. To see that information, we add env to the exposure.include property.
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar \
--management.endpoints.web.exposure.include=conditions,env
13.7. Actuator Links
This adds a full /env endpoint and a view specific /env/{property} endpoint to see information
for a specific property name. The available Actuator links are available at http://localhost:8080/actuator.
{
_links: {
self: {
href: "http://localhost:8080/actuator",
templated: false
},
conditions: {
href: "http://localhost:8080/actuator/conditions",
templated: false
},
env: {
href: "http://localhost:8080/actuator/env",
templated: false
},
env-toMatch: {
href: "http://localhost:8080/actuator/env/{toMatch}",
templated: true
}
}
}
13.8. Actuator Environment Report
The Actuator Environment Report is available at http://localhost:8080/actuator/env.
{
activeProfiles: [ ],
propertySources: [{
name: "server.ports",
properties: {
local.server.port: {
value: 8080
}
}
},
{
name: "commandLineArgs",
properties: {
management.endpoints.web.exposure.include: {
value: "conditions,env"
}
}
},
...
13.9. Actuator Specific Property Source
The source of a specific property and its defined value is available below the /actuator/env URI
such that the hello.greeting property is located at
http://localhost:8080/actuator/env/hello.greeting.
{
property: {
source: "applicationConfig: [classpath:/application.properties]",
value: "application.properties Says - Hey"
},
...
13.10. More Actuator
We can explore some of the other Actuator endpoints by changing the include property to * and revisiting the main actuator endpoint. Actuator Documentation is available on the web.
$ java -jar target/appconfig-autoconfig-*-SNAPSHOT-bootexec.jar \
--management.endpoints.web.exposure.include="*" (1)
| 1 | double quotes ("") being used to escape * special character on command line |
14. Summary
In this module we:
-
Defined conditions for
@Configuration/@AutoConfigurationclasses and@Beanfactory methods that are evaluated at runtime startup -
Placed User-defined
@Configurationconditions, which are evaluated first, in with application module -
Placed
@AutoConfigurationclasses in separatestartermodule 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
@AutoConfigurationclasses 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
debugproperty or by using the Actuator for web applications