How To Extend and Customize Auto-Configuration Classes Provided by Spring Boot

Written by adgadev | Published 2022/08/31
Tech Story Tags: spring-boot | spring | java | programming | auto-configuration | auto-configuration-classes | tutorial | debugging

TLDRSpring boot 2.4 introduced concept of 'profile groups' which allows expanding single profile into multiple sub-profiles. We can use profile groups to map a single profile identifying environment where application is running (dev / stage / prod) to set of features which are enabled at each environment. In order to use profile groups we need to define spring.profiles.group section in application.yml.via the TL;DR App

Problem Statement

Imagine a scenario when we need to enable certain spring boot auto-configuration on a subset of environments only. The reason for that may be that i.e. our new feature will use new database (mongo) which hasn’t been setup on all environments yet for some reason, but we don’t want this issue to stop us from deploying new version of the application.

Not all spring boot auto-configurations support disabling them using property. We could try to exclude auto-configurations by declaring them in exclusions property of @SpringBootApplication and later import these auto-configurations using @Import or @ImportAutoConfiguration in profile specific configuration. Unfortunately this approach often does not work. Basically mixing auto-configuration and manual imports often leads to hard to diagnose configuration errors. Fortunately there is a better way which is purely based on auto-configuration.

Profile Groups

Spring boot 2.4 introduced concept of 'profile groups' which allows expanding single profile into multiple sub-profiles. We can use profile groups to map a single profile identifying environment where application is running (dev / stage / prod) to set of features which are enabled at each environment. In order to use profile groups we need to define spring.profiles.group section in application.yml

https://gist.github.com/adgadev/1148317c3f486ef49847575b6eea8801

Such setup will result in having active profiles: dev, bravo, halo on dev environment. On prod environment only prod and bravo profiles will be active.

Feature Specific Auto-Configuration

In our case we will be building a feature based on mongoDb. When feature is disabled, the application should not require mongo database to exist nor utilize any mongo auto-configurations, since it’s the only feature using mongodb in our app. Apart from that all services, controllers and other application spring beans related with this feature should be created only when feature is enabled.

For start, we need to define annotation which will allow binding feature specific components & configurations with a dedicated spring profile.

https://gist.github.com/adgadev/03ea32b69a132e47571a6253c2b67f52

Next thing is to add auto-configuration’s condition which will allow enabling feature specific auto-configurations when feature profile is active:

https://gist.github.com/adgadev/5d34f1605ec708185bebbc7577ac063a

There are three mongo related auto-configurations used by our application. They need to be made conditional. Let’s create new auto-configuration classes which are subclasses of original auto-configurations and annotate them with @Conditional utilizing feature condition:

https://gist.github.com/adgadev/48209579dd89b93e69b16718aa5b221b

https://gist.github.com/adgadev/5994adedaf5294b480c096e221e13031

https://gist.github.com/adgadev/dcc5dd01b0102d16504b715f35061015

Unfortunately this is not sufficient since spring boot conditional rules are not being inherited by subclasses. Hence, conditional rules has to be copied from MongoAutoConfiguration, MongoDataAutoConfiguration and MongoRepositoriesAutoConfiguration to their subclasses.

The other thing is that dependencies declared in @AutoConfigureAfter / @AutoConfigureBefore should refer to auto-configuration classes, not their subclasses. Otherwise, they won’t work. That’s why these annotations has to be copied from superclass to subclasses, but this time values inside annotations has to be replaced with corresponding Halo*AutoConfiguration classes.

Other spring’s annotations used in auto-configuration subclasses like i.e. @Import or @EnableConfigurationProperties will work as if they were part of subclass auto-configuration, so there is no need to copy them from subclass.

After applying these changes we get following auto-configuration classes:

https://gist.github.com/adgadev/17a9b8baf7c0cc98f30fd99c5d409ccd

https://gist.github.com/adgadev/d67aa167151fc1887f15086933bfcc6e

https://gist.github.com/adgadev/ef869a40c2bfc6b6d9085d1a3eb9666c

New auto-configurations have to be registered in META-INF/spring.factories file:

https://gist.github.com/adgadev/383267f2c3bc5691f350998516c058ca

Now let’s exclude original mongo auto-configurations:

https://gist.github.com/adgadev/bee259e095658fd535538b08c330325c

Applying Solution to Sample Feature

Having conditional mongo setup in place we can add simple mongo dependant feature called halo. The feature consists of single document, mongo repository and a service. Repository and service beans are created only if the halo feature is enabled.

https://gist.github.com/adgadev/36c86b9d52cf0745cbc6c28edf5063b2

https://gist.github.com/adgadev/7f05611ccb443cc9f8c0c1fd6ec6c0a0

https://gist.github.com/adgadev/d42d5b1bb8fd2522164876c22b9b5072

https://gist.github.com/adgadev/fd2aa86af5adafcd11c75a6cac58e4d6

There is also spring configuration which enables mongock framework for document migration and explicitly defines mongo repositories package:

https://gist.github.com/adgadev/caf8681d8c18e4d8f894c815ce5e66e2

Now let’s test how this feature works assuming it’s enabled only on dev and mongo database is present there only.

https://gist.github.com/adgadev/77d718f1f82a395c2fab998831d62dd8

Test is very simple. It creates mongo database using testcontainers, starts spring context and tests haloService in such environment. The test is green when executed.

https://gist.github.com/adgadev/61c9ff64ecec82c8260fec96f64564ea

Test for prod env shows that application context starts successfully, despite the fact that there is no mongo database configured. None mongo or halo related spring been is constructed.

https://gist.github.com/adgadev/e349fdc683ac3cfa1b61cc93bfe1c519

The same test, but with halo feature enabled fails on spring context creation, due to connectivity issues to mongo database when instantiating mongock’s beans.

https://gist.github.com/adgadev/e9846f5195bbc75d15e2b1fa2aed41c4

Summary

The full source code of the examples is available here.

Also published here.


Written by adgadev | Software Engineer
Published by HackerNoon on 2022/08/31