Workshop @ Spring I/O 2025 - Moritz Halbritter & Fabian Krüger
Getting Started
-
Checkout the workshop project from GitHub
cd ~
git clone https://github.com/fabapp2/spring-boot-magic-workshop.git
cd spring-boot-magic-workshop
git checkout exercise-1
Tip
|
Remember to reload your workspace after adding dependencies to make your IDE aware of the new types. |
Project Layout
The project has these modules following the app continuum layout.
-
app
- The Spring Boot application -
library-autoconfigure
- Spring Boot Auto-configuration for thelibrary
-
library-api
- Spring Boot independent library withGreetingService
interface -
library-stdout
- Spring Boot independent library implementation logging to stdout -
library-slf4j
- Spring Boot independent library implementation logging with SLF4J -
library-spring-boot-starter
- A Spring Boot starter to make the auto-configuration and all dependencies easily available
Prerequisite
Start the application in app
and understand how modules are wired.
Exercise 1: Auto Configured Bean
Make the StdOutGreetingService
from library-stdout
available as an auto-configured Spring bean for applications that rely on GreetingService
.
Learnings
-
How does Spring Boot find auto-configuration classes
-
How does Spring Boot provide auto-configured beans
-
How to allow auto-configured beans to be overwritten
Task
-
Create an auto-configuration class
com.workshop.magic.config.GreetingAutoConfiguration
in thelibrary-autoconfigure
module. -
Add a Maven dependency to
org.springframework.boot:spring-boot-autoconfigure
which brings the@AutoConfiguration
annotation. -
In the
library-autoconfigure
module, add a Maven dependency to other required modules (library-api
andlibrary-stdout
). Make them optional, which is a best-practice for auto-configurations. -
Annotate the
GreetingAutoConfiguration
with@AutoConfiguration
. -
Annotate the
GreetingAutoConfiguration
with@ConditionalOnClass(GreetingService.class)
to only process the auto-config whenGreetingService
is on the application classpath. -
This auto-configuration should provide
StdOutGreetingService
as a Spring bean of typeGreetingService
. Use the@Bean
annotation for that. -
Auto-configuration classes must be declared in a classpath resource file named
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
. This file contains fully qualified names of the auto-configuration so that Spring Boot can find them. -
Add the Maven dependency
library-autoconfigure
andlibrary-stdout
toapp
, making the auto-configuredGreetingService
available. -
🤔 Starting the application should fail. Why is that?
-
To avoid conflicts and allow custom implementations, the
StdOutGreetingService
should only be created when no other bean of typeGreetingService
exists. This can be achieved by annotating the bean declaration with@ConditionalOnMissingBean
, which tells Spring Boot to back off when such a bean already exists. -
✅ Starting the application should now print: MyGreetingService: Hola Spring I/O Barcelona.
-
Modify the application to use the
StdOutGreetingService
now. -
✅ Starting the application should now print: StdOutGreetingService: Hola Spring I/O Barcelona.
Note
|
Auto-configurations must be loaded only by being named in the imports file. Make sure that they are defined in a specific package space and that they are never the target of component scanning. Furthermore, auto-configuration classes should not enable component scanning to find additional components. Specific @Import annotations should be used instead. |
Detailed Steps
Detailed Steps
-
Create a new class
com.workshop.magic.config.GreetingAutoConfiguration
in thelibrary-autoconfigure
module. -
Add a Maven dependency to
org.springframework.boot:spring-boot-autoconfigure
in thelibrary-autoconfigure
module. -
Add a Maven dependency to
com.workshop:library-stdout
in thelibrary-autoconfigure
module, with<optional>true</optional>
. -
Create a new file
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
in thelibrary-autoconfigure
module (see the reference documentation). -
Add the fully qualified classname of the
GreetingAutoConfiguration
class to the.imports
file. -
Annotate the
GreetingAutoConfiguration
with@AutoConfiguration
. -
Create a new
GreetingService
bean inGreetingAutoConfiguration
that returns a new instance ofStdOutGreetingService
.@Bean GreetingService stdOutGreetingService() { return new StdOutGreetingService(); }
-
Add a Maven dependency to
com.workshop:library-autoconfigure
in theapp
module. -
Add a Maven dependency to
com.workshop:library-stdout
in theapp
module. -
Starting the application fails. That’s because there are now two beans of type
GreetingService
:MyGreetingService
(annotated with@Service
) from theapp
module and theStdOutGreetingService
from the auto-configuration. -
Use the
@ConditionalOnMissingBean
annotation on theGreetingService
bean method inGreetingAutoConfiguration
to only load the bean when no other bean of typeGreetingService
exists (see the reference documentation). -
The application now starts and uses the
MyGreetingService
. -
Now, remove the
MyGreetingService
class from theapp
module, or comment out/remove the@Service
annotation onMyGreetingService
. -
The application now starts and uses the
StdOutGreetingService
.
Conclusion
Think for a moment, when is this useful and where does Spring Boot use this concept?
Answer
Spring Boot’s auto-configuration simplifies application development by automatically configuring components based on the dependencies present on the classpath. This feature reduces the need for manual setup, allowing developers to focus on business logic rather than boilerplate code.
For example, adding spring-boot-starter-web
sets up a whole webserver without manual configuration.
Solution
git checkout -f exercise-2
🥳 Fantastic, let’s move on to the next exercise
Exercise 2: Custom Spring Boot Starter
It’s a bit unfortunate that users of your auto-configuration need a dependency on com.workshop:library-autoconfigure
and on com.workshop:library-stdout
.
You will now package the library-autoconfigure
and library-stdout
modules into a reusable Spring Boot starter.
Learnings
-
How do Spring Boot Starters work
Task:
-
Add Maven dependencies to
library-autoconfigure
andlibrary-stdout
in thelibrary-spring-boot-starter
module. -
Add a Maven dependency to
org.springframework.boot:spring-boot-starter
in thelibrary-spring-boot-starter
module. -
Replace direct dependencies to
library-autoconfigure
andlibrary-stdout
in theapp
module with the new starter. -
✅ Confirm that the app still works as expected and prints the greeting.
Conclusion
🤔 Why create a starter? When could that be useful?
Answer
A starter simplifies the integration of your library. It contains the auto-configuration and all the needed dependencies in one single dependency. In our case, the starter only contains two dependencies, but you can image starters for more complex scenarios, which bring dozens or more dependencies.
Solution
git checkout -f exercise-3
🥳 Awesome, let’s move on to the next exercise
Exercise 3: Custom Starter Without Default GreetingService
In this exercise, you will make the existing LoggerGreetingService
available as an auto-configured bean — but only when the corresponding class is on the classpath. You will also adjust the fallback behavior of StdOutGreetingService
so it is only used when the SLF4J-based implementation is not present.
This pattern mimics common practices in Spring Boot where auto-configured beans adapt to the available classpath.
Learnings
-
How to auto-configure beans conditionally based on classpath presence
-
How to combine
@ConditionalOnClass
and@ConditionalOnMissingClass
-
How to selectively expose features outside the default starter
Task
-
In the
library-autoconfigure
module add an optional dependency tolibrary-slf4j
. -
In the
GreetingAutoConfiguration
, register an additionalGreetingService
bean that returns aLoggerGreetingService
instance. -
Annotate this method with:
-
@ConditionalOnClass(LoggerGreetingService.class)
— to create a bean only ifLoggerGreetingService
is on the classpath. -
@ConditionalOnMissingBean
— to allow overriding by users.
-
-
Update the existing
StdOutGreetingService
bean:-
Add
@ConditionalOnMissingClass("com.workshop.magic.service.slf4j.LoggerGreetingService")
— to create the bean only ifLoggerGreetingService
is not on the classpath.
-
-
Ensure the module
library-slf4j
is not included in thelibrary-spring-boot-starter
module. -
In the
app
module, add a Maven dependency tolibrary-slf4j
. -
✅ Start the app: You should see LoggerGreetingService: Hola Spring I/O Barcelona.
-
Remove the
library-slf4j
Maven dependency again: -
✅ Start the app: You should now see StdOutGreetingService: Hola Spring I/O Barcelona.
Detailed Steps
Detailed Steps
-
In the
library-autoconfigure
module add a dependency tolibrary-slf4j
with:<dependency> <groupId>com.workshop</groupId> <artifactId>library-slf4j</artifactId> <optional>true</optional> </dependency>
-
In the
GreetingAutoConfiguration
class, add this bean method:@Bean @ConditionalOnMissingBean @ConditionalOnClass(LoggerGreetingService.class) GreetingService slf4jGreetingService() { return new LoggerGreetingService(); }
-
On the existing
stdOutGreetingService()
method, add:@ConditionalOnMissingClass("com.workshop.magic.service.slf4j.LoggerGreetingService")
-
In the
library-spring-boot-starter
module, ensurelibrary-slf4j
is not added as a dependency. Onlylibrary-api
(not necessarily needed, as it comes transitively throughlibrary-stdout
),library-stdout
, andlibrary-autoconfigure
should be included.<dependency> <groupId>com.workshop</groupId> <artifactId>library-autoconfigure</artifactId> </dependency> <dependency> <groupId>com.workshop</groupId> <artifactId>library-api</artifactId> </dependency> <dependency> <groupId>com.workshop</groupId> <artifactId>library-stdout</artifactId> </dependency>
-
Make sure the
app
module declares a dependency tolibrary-slf4j
with:<dependency> <groupId>com.workshop</groupId> <artifactId>library-slf4j</artifactId> </dependency>
-
Run the application.
LoggerGreetingService
is now used, as it’s on the classpath. TheStdOutGreetingService
bean isn’t created, asLoggerGreetingService
is on the classpath. -
Remove the
library-slf4j
dependency from theapp
module and re-run it. -
StdOutGreetingService
is now used, asLoggerGreetingService
is not on the classpath.
Conclusion
This pattern of classpath-based behavior is common in real-world Spring Boot libraries. It allows default behavior that can be changed or enhanced by simply adding another dependency — without requiring configuration or code changes.
🤔 Can you think of an example where it is done this way?
Answer
Spring Boot uses classpath detection extensively to toggle features. For example, if Hibernate is on the classpath, JPA support is auto-configured. If it isn’t, Spring Boot silently skips it. This reduces configuration overhead and provides smart defaults that adapt to the environment.
The spring-boot-starter-data-jpa
starter doesn’t include a database driver, because the Spring Boot team doesn’t want to force a database choice on you.
You’ll need to add one for yourself, for example adding org.postgresql:postgresql
auto-configures a DataSource
which can talk to PostgreSQL.
Solution
git checkout -f exercise-4
🥳 Superb, let’s move on to the next exercise
Exercise 4: Conditions Evaluation Report
In this exercise, you’ll learn how to leverage Spring Boot’s Conditions Evaluation Report to understand why certain auto-configurations are applied or not. This is especially useful when troubleshooting unexpected behavior in your application.
Learnings
-
How to enable and interpret the Conditions Evaluation Report
-
How to identify why certain beans are or aren’t loaded
Task
-
Enable debug mode in your application to view the Conditions Evaluation Report:
debug=true
This can be added to your
application.properties
file or passed as a command-line argument using--debug
. -
Start your application. Upon startup, you should see a detailed report in the console that looks like:
=========================== CONDITIONS EVALUATION REPORT =========================== Positive matches: ----------------- ... Negative matches: ----------------- ...
This report lists all auto-configuration classes with their conditions, and they were applied or not.
-
Review the report in regard to
GreetingAutoConfiguration
and understand which configurations were applied and which were not, along with the reasons. -
Use this information to troubleshoot any unexpected behavior or to verify that your custom configurations are being considered appropriately.
Conclusion
The Conditions Evaluation Report is a powerful tool for diagnosing configuration issues in Spring Boot applications. By understanding which conditions are met or not, you can gain insights into the auto-configuration process and ensure your application behaves as expected.
Solution
git checkout -f exercise-5
🥳 Great job! Let’s proceed to the next exercise.
Exercise 5: Property Configuration and @ConditionalOnProperty
Learnings
-
How to parametrize auto-configured beans (task A)
-
How to create auto-configured beans depending on properties (task B)
Task A
-
There’s a
GreetingProperties
class in thelibrary-autoconfigure
module which should be filled with values from theapplication.properties
. -
Annotate the
GreetingAutoConfiguration
with@EnableConfigurationProperties(GreetingProperties.class)
to enable loading the values from theapplication.properties
. -
Annotate
GreetingProperties
with@ConfigurationProperties
and bind it to theworkshop.greeting
prefix. -
The
StdOutGreetingService
and theLoggerGreetingService
have constructors which allows you to customize the greeting. By default, it’s "Hola", but this should now be configurable via theapplication.properties
by settingworkshop.greeting.text
. -
Change the bean methods for
StdOutGreetingService
andLoggerGreetingService
to injectGreetingProperties
and configure the greeting prefix. -
Add a property
workshop.greeting.text=Gude
toapplication.properties
. -
✅ Start the application. It should now print LoggerGreetingService: Gude Spring I/0 Barcelona or StdOutGreetingService: Gude Spring I/0 Barcelona.
Detailed Steps A
Detailed Steps
-
In
library-autoconfigure
, annotateGreetingAutoConfiguration
with:@EnableConfigurationProperties(GreetingProperties.class)
-
In the same module open the
GreetingProperties
class and annotate it with:@ConfigurationProperties(prefix = "workshop.greeting")
-
In
GreetingAutoConfiguration
, injectGreetingProperties
into bothGreetingService
bean methods:GreetingService stdOutGreetingService(GreetingProperties properties) GreetingService slf4jGreetingService(GreetingProperties properties)
-
Replace the constructor calls with:
new StdOutGreetingService(properties.getText()) new LoggerGreetingService(properties.getText())
-
In
application.properties
set the following:workshop.greeting.text=Gude
-
Run the application
-
✅ You should see LoggerGreetingService: Gude Spring I/0 Barcelona or StdOutGreetingService: Gude Spring I/0 Barcelona now.
Task B
-
Now, we want a property called
workshop.greeting.type
which controls the type ofGreetingService
that will be used:-
workshop.greeting.type=logger
should create aLoggerGreetingService
bean. -
workshop.greeting.type=stdout
should create aStdOutGreetingService
bean.
-
-
You can use
@ConditionalOnProperty
for that. Annotate both bean methods with@ConditionalOnProperty
and set the annotation attributes accordingly. -
Remove the
@ConditionalOnMissingClass
from theStdOutGreetingService
bean method. -
Add
workshop.greeting.type=stdout
to yourapplication.properties
-
✅ Start the application. It should now print StdOutGreetingService: Gude Spring I/0 Barcelona.
-
Change
workshop.greeting.type
tologger
. -
✅ Start the application. It should now print LoggerGreetingService: Gude Spring I/0 Barcelona.
-
Remove the
workshop.greeting.type
fromapplication.properties
-
🤔 Start the application. It now fails. Why is that?
-
Change the annotation attributes from
@ConditionalOnProperty
on theStdOutGreetingService
to also match if the property is missing. -
✅ Start the application. It should now print StdOutGreetingService: Gude Spring I/0 Barcelona.
Detailed Steps
Detailed Steps
-
Annotate the
StdOutGreetingService
bean method with:@ConditionalOnProperty(name = "workshop.greeting.type", havingValue = "stdout")
-
Remove the
@ConditionalOnMissingClass
annotation from theStdOutGreetingService
bean method -
Annotate the
LoggerGreetingService
bean method with:@ConditionalOnProperty(name = "workshop.greeting.type", havingValue = "logger")
-
In
application.properties
set the following:workshop.greeting.type=stdout
-
Run the application.
-
✅ You should see: StdOutGreetingService: Gude Spring I/0 Barcelona
-
In
application.properties
set the following:workshop.greeting.type=logger
-
Run the application.
-
✅ You should see: LoggerGreetingService: Gude Spring I/0 Barcelona
TipThe LoggerGreetingService
bean will only be created iflibrary-slf4j
is on the classpath. If not, eventype=logger
will not work. -
Remove the
workshop.greeting.type
line and restart the app. -
Startup of the app fails, because there’s no
GreetingService
available. You can use the Conditions Evaluation Report to find out why. -
Change the annotation of the
StdOutGreetingService
bean method inGreetingAutoConfiguration
to look like this:@ConditionalOnProperty(name = "workshop.greeting.type", havingValue = "stdout", matchIfMissing = true)
-
Run the application.
-
✅ You should see: StdOutGreetingService: Gude Spring I/0 Barcelona
Conclusion
In this exercise, you learned how to read properties from application.properties
and use the values to configure your beans.
This can not only be used to configure beans, but it can also be used to influence which beans are created at all.
Using @ConditionalOnProperty
, you can activate specific beans based on the application’s configuration, enabling powerful runtime flexibility.
This allows users to influence the behavior using simple property values, without needing to write their own bean overrides.
🤔 Why is this useful in real-world Spring Boot applications?
Answer
It allows configuring beans provided through auto-configuration and changing their behavior without the need to change the bean declaration itself. This enables teams to toggle functionality through properties, and provides sensible defaults with the ability to override them.
An example in Spring Boot would be the management.server.port
property. If set, an additional webserver is started on the management port which provides access to actuator, etc.
A lot of beans are created under the hood to make that happen, all controlled by a single user-visible property.
Solution
git checkout -f exercise-6
🥳 Superb, let’s move on to the next exercise
Exercise 6: Using Custom Conditions
It is also possible to create custom conditions like the existing @On…
conditions from Spring Boot.
Let’s create a custom condition that checks the system property my.custom.condition
- just because it’s simple.
But imagine you have a more sophisticated custom check here, e.g., infrastructure checks like the Kubernetes probes.
Or you could write a condition which triggers only on 1st of April.
Oh, the possibilities!
Learnings
-
How to create your own conditions
-
How to use that custom condition
Task
-
Create a new annotation
@MyCustomCondition
in thelibrary-autoconfigure
module. It must have a@Target
ofTYPE
andMETHOD
and a@Retention
ofRUNTIME
(you can also copy that from Spring Boot’s@ConditionalOnProperty
). -
The newly created annotation must be annotated with
@Conditional({OnCustomCondition.class})
. -
A new class,
OnCustomCondition
must be created. It should extend Spring Boot’sSpringBootCondition
. -
The
getMatchOutcome
method must be overriden and should check themy.custom.condition
system property. UseConditionOutcome.match
andConditionOutcome.noMatch
to signal if the condition matches or not. -
Modify the
GreetingAutoConfiguration
to use the new@MyCustomCondition
. A bean of classBeepGreetingService
(located in thelibrary-slf4j
module) should be created if@MyCustomCondition
matches. -
Test that the application works by setting the system property
my.custom.condition
and verify that theBeepGreetingService
bean is used.NoteYou’ll have to set workshop.greeting.type
to something else thanlogger
orstdout
, because otherwise theLoggerGreetingService
orStdOutGreetingService
is also created. -
🤔 Also take a look at the conditions evaluation report. Do you see your condition in there?
Detailed Steps
Detailed Steps
-
Create a new annotation in the
library-autoconfigure
module, calledMyCustomCondition
-
Annotate the annotation with
@Target({ElementType.TYPE, ElementType.METHOD})
and with@Retention(RetentionPolicy.RUNTIME)
-
Annotate the annotation with
@Conditional({OnCustomCondition.class})
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Conditional({OnCustomCondition.class}) @interface MyCustomCondition { }
-
Create a class called
OnCustomCondition
and let it extendSpringBootCondition
-
Implement the
getMatchOutcome
method-
Use
System.getProperty("my.custom.condition")
to read themy.custom.condition
system property -
If the value of that property is
true
, returnConditionOutcome.match
to signal that the condition matches -
Otherwise, return
ConditionOutcome.noMatch
to signal that the condition didn’t matchclass OnCustomCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { String value = System.getProperty("my.custom.condition"); if (value == null) { return ConditionOutcome.noMatch("No 'my.custom.condition' system property found"); } if (value.toLowerCase(Locale.ROOT).equals("true")) { return ConditionOutcome.match("'my.custom.condition' system property is true"); } return ConditionOutcome.noMatch("'my.custom.condition' system property is '%s'".formatted(value)); } }
-
-
Add a new
@Bean
method to theGreetingAutoConfiguration
class, call itbeepGreetingService
, its return type isGreetingService
-
Annotate this new method with
@MyCustomCondition
,@ConditionalOnMissingBean
and@ConditionalOnClass(BeepGreetingService.class)
-
Return a new instance of
BeepGreetingService
from that method@Bean @ConditionalOnMissingBean @MyCustomCondition @ConditionalOnClass(BeepGreetingService.class) GreetingService beepGreetingService(GreetingProperties properties) { return new BeepGreetingService(properties.getPrefix()); }
-
-
To test the custom condition, you can add
System.setProperty("my.custom.condition", "true");
as first line in themain
method, or you can set the system properties when starting with your IDE -
You’ll also need to add
workshop.greeting.type=none
to yourapplication.properties
, because otherwise theLoggerGreetingService
or theStdOutGreetingService
would be created
Conclusion
Can you image why it is useful to create custom conditions?
Answer
Creating your own conditions is useful if the conditions from Spring Framework and Spring Boot don’t fit your needs. Custom conditions show the power of an extensible framework like the Spring Framework. There’s no "magic" behind the built-in Spring Boot conditions — they are built on the same foundations like your custom condition is.
Note
|
You can take a look at the @Profile annotation from Spring Framework: The logic is implemented in ProfileCondition , and it essentially returns true if the profile is activated and false if not.
|
Solution
git checkout -f exercise-7
🥳 Phenomenal, let’s move on to the next exercise
Exercise 7: Testing The Auto-Configuration
Create unit tests to ensure that the GreetingAutoConfiguration
works as expected.
Task
-
A Maven dependency on
org.springframework.boot:spring-boot-starter-test
with scopetest
has to be added in thelibrary-autoconfigure
module. -
A test class for the
GreetingAutoConfiguration
class must be created. -
Spring Boot’s
ApplicationContextRunner
should be used to test the auto-configuration (see the reference documentation). -
AssertJ assertions should be used to verify that the context contains a
StdOutGreetingService
bean if no property is set. -
Implement tests for these use cases:
-
The context contains a
StdOutGreetingService
bean if the propertyworkshop.greeting.type
is set tostdout
. -
The context contains a
LoggerGreetingService
bean if the propertyworkshop.greeting.type
is set tologger
. -
The context contains
BeepGreetingService
bean if the system propertymy.custom.condition
is set totrue
. -
That user-defined beans take precedence over the auto-configured
GreetingService
beans — essentially testing that@ConditionalOnMissingBean
works.
-
Detailed Steps
Detailed Steps
-
Add a Maven dependency to
org.springframework.boot:spring-boot-starter-test
with scopetest
to thelibrary-autoconfigure
module. -
Create a class named
GreetingAutoConfigurationTest
inlibrary-autoconfigure/src/test/java
in the packagecom.workshop.magic.config
. -
Create a field of type
ApplicationContextRunner
, and use the fluent API to callwithConfiguration
withAutoConfigurations.of(GreetingAutoConfiguration.class)
.private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(GreetingAutoConfiguration.class));
-
Write a test case named
shouldProvideStdOutGreetingServiceByDefault
which uses therun
method of theApplicationContextRunner
field.-
Inside the lambda block of the
run
method, use AssertJ’sassertThat
on the context to callhasSingleBean
with anStdOutGreetingService.class
argument.@Test void shouldProvideStdOutGreetingServiceByDefault() { this.contextRunner.run(context -> { assertThat(context).hasSingleBean(StdOutGreetingService.class); }); }
-
-
Write a test case named
shouldProvideStdOutGreetingServiceWhenPropertyIsSet
which uses thewithPropertyValues
of theApplicationContextRunner
field to set the propertyworkshop.greeting.type
tostdout
.-
Inside the lambda block of the
run
method, use AssertJ’sassertThat
on the context to callhasSingleBean
with anStdOutGreetingService.class
argument.@Test void shouldProvideStdOutGreetingServiceWhenPropertyIsSet() { this.contextRunner .withPropertyValues("workshop.greeting.type=stdout") .run(context -> { assertThat(context).hasSingleBean(StdOutGreetingService.class); }); }
-
-
Write a test case named
shouldProvideLoggerGreetingServiceWhenPropertyIsSet
which uses thewithPropertyValues
of theApplicationContextRunner
field to set the propertyworkshop.greeting.type
tologger
.-
Inside the lambda block of the
run
method, use AssertJ’sassertThat
on the context to callhasSingleBean
with anLoggerGreetingService.class
argument.
-
-
Write a test case named
shouldProvideBeepGreetingServiceIfSystemPropertyIsSet
which useswithPropertyValues
of theApplicationContextRunner
field to set the propertyworkshop.greeting.type
tonone
.-
Additionally, it uses the
withSystemProperties
method to setmy.custom.condition
totrue
. -
Inside the lambda block of the
run
method, use AssertJ’sassertThat
on the context to callhasSingleBean
with anBeepGreetingService.class
argument.@Test void shouldProvideBeepGreetingServiceIfSystemPropertyIsSet() { this.contextRunner .withPropertyValues("workshop.greeting.type=none") .withSystemProperties("my.custom.condition=true") .run(context -> { assertThat(context).hasSingleBean(BeepGreetingService.class); }); }
-
-
Write a test case named
shouldBackOffIfGreetingServiceIsDefinedByUser
which uses thewithBean
method of theApplicationContextRunner
field to define a bean of typeGreetingService
. Create an inner static class or an anonymous class for the "user provided"GreetingService
.-
Inside the lambda block of the
run
method, use AssertJ’sassertThat
on the context to callhasSingleBean
with anGreetingService.class
argument.@Test void shouldBackOffIfGreetingServiceIsDefinedByUser() { this.contextRunner .withBean(GreetingService.class, UserGreetingService::new) .run(context -> { assertThat(context).hasSingleBean(GreetingService.class); assertThat(context).hasSingleBean(UserGreetingService.class); }); } private static class UserGreetingService implements GreetingService { @Override public void greet(String name) { System.out.println("UserGreetingService: Hello " + name); } }
-
Conclusion
What’s the benefit of writing a unit test for an auto-configuration?
Answer
Auto-configurations can contain a lot of conditions, sometimes even custom ones. As this auto-configuration is part of your codebase,
you should also unit-test it to ensure that it behaves as designed, same as the rest of your code.
Spring Boot’s ApplicationContextRunner
makes this easy.
Solution
git checkout -f exercise-8
🥳 Brilliant, let’s move on to the next exercise
Exercise 8: Adding properties metadata
Use the Spring Boot configuration processor to generate metadata for your configuration properties.
Task
-
Add the
org.springframework.boot:spring-boot-configuration-processor
to thelibrary-autoconfigure
module. -
Run a build and inspect the
components/library-autoconfigure/target/classes/META-INF/spring-configuration-metadata.json
file. -
🤔 Think about why that file could be useful.
-
The
text
property inGreetingProperties
should be renamed toprefix
, while deprecating thetext
property. Use@Deprecated
and@DeprecatedConfigurationProperty
annotations to achieve this. -
Run a build and inspect the file
spring-configuration-metadata.json
again. -
🤔 What has changed? Why could that be useful?
-
🤔 Open the
application.properties
in your IDE. Do you notice something? -
Add
org.springframework.boot:spring-boot-properties-migrator
to theapp
module. -
Start the app and observe the console output.
Detailed Steps
Detailed Steps
-
Add
org.springframework.boot:spring-boot-configuration-processor
tocomponents/library-autoconfigure/pom.xml
, withoptional = true
.<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
-
Newer Java versions require an explicit configuration for annotation processors. Configure the
maven-compiler-plugin
to includeorg.springframework.boot:spring-boot-configuration-processor
as an annotation processor. You can take a look at the POM file generated by start.spring.io for an example.<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
-
Run
./mvnw compile
and inspectcomponents/library-autoconfigure/target/classes/META-INF/spring-configuration-metadata.json
. -
Replace
private String text
in theGreetingProperties
class withprivate String prefix
. -
Annotate the
public String getText()
method with@Deprecated
and with@DeprecatedConfigurationProperty(replacement = "workshop.greeting.prefix")
. -
Return
this.prefix
from thegetText()
method. -
Assign
this.prefix
in thesetText()
method. -
Add a new getter and setter method for
private String prefix
.private String prefix = "Hello"; @DeprecatedConfigurationProperty(replacement = "workshop.greeting.prefix") @Deprecated public String getText() { return this.prefix; } public void setText(String text) { this.prefix = text; } public String getPrefix() { return this.prefix; } public void setPrefix(String prefix) { this.prefix = prefix; }
-
Run
./mvnw compile
and inspectcomponents/library-autoconfigure/target/classes/META-INF/spring-configuration-metadata.json
. -
Add
org.springframework.boot:spring-boot-properties-migrator
withscope = runtime
toapp/app/pom.xml
. -
Run the application
Conclusion
Why is providing that netadata file beneficial? Who would use it?
Answer
This metadata file is read by IDEs to provide auto-completion for properties.
Additionally, deprecations and their replacement are also recorded in that file, which is also used by IDEs to guide users.
And the spring-boot-properties-migrator
also uses this file to display deprecations on startup and to provide the automatic mapping from the old property to the new one.
Solution
git checkout -f main
🥳 Congrats, you finished all exercises! We hope you enjoyed the learnings.
Feedback
We’d love your feedback on this workshop! If you enjoyed it or have ideas for improvement, please reach out or connect with us on social media or via the conference app. Thanks for helping us get better! ❤️ ️
Connect With Us!
-
Moritz Halbritter
-
Fabian Krüger