How To Instantiatiate Multiple Beans Dinamically in Spring-Boot Depending on Configuration-Properties
TL;DR
In this mini-HowTo I will show a way, how to instantiate multiple beans dinamically in Spring-Boot, depending on configuration-properties. We will:
- write a
ApplicationContextInitializer
to add the beans to the context, before it is refreshed - write a
EnvironmentPostProcessor
to access the configured configuration sources - register the
EnvironmentPostProcessor
with Spring-Boot
Write an ApplicationContextInitializer
Additionally Beans can be added programatically very easy with the help of an ApplicationContextInitializer
:
@AllArgsConstructor
public class MultipleBeansApplicationContextInitializer
implements
ApplicationContextInitializer
{
private final String[] sites;
@Override
public void initialize(ConfigurableApplicationContext context)
{
ConfigurableListableBeanFactory factory =
context.getBeanFactory();
for (String site : sites)
{
SiteController controller =
new SiteController(site, "Descrition of site " + site);
factory.registerSingleton("/" + site, controller);
}
}
}
This simplified example is configured with a list of strings that should be registered as controllers with the DispatcherServlet
.
All “sites” are insances of the same controller SiteController
, which are instanciated and registered dynamically.
The instances are registered as beans with the method registerSingleton(String name, Object bean)
of a ConfigurableListableBeanFactory
that can be accessed through the provided ConfigurableApplicationContext
The array of strings represents the accessed configuration properties in the simplified example. The array will most probably hold more complex data-structures in a real-world application.
But how do we get access to the configuration-parameters, that are injected in this array here…?
Accessing the Configured Property-Sources
Instantiating and registering the additionally beans is easy.
The real problem is to access the configuration properties in the early plumbing-stage of the application-context, in that our ApplicationContextInitializer
runs in:
The initializer cannot be instantiated and autowired by Spring!
The Bad News: In the early stage we are running in, we cannot use autowiring or access any of the other beans that will be instantiated by spring – especially not any of the beans, that are instantiated via @ConfigurationProperties
, we are intrested in.
The Good News: We will present a way, how to access initialized instances of all property sources, that will be presented to your app
Write an EnvironmentPostProcessor
If you write an EnvironmentPostProcessor
, you will get access to an instance of ConfigurableEnvironment
, that contains a complete list of all PropertySource
‘s, that are configured for your Spring-Boot-App.
public class MultipleBeansEnvironmentPostProcessor
implements
EnvironmentPostProcessor
{
@Override
public void postProcessEnvironment(
ConfigurableEnvironment environment,
SpringApplication application)
{
String sites =
environment.getRequiredProperty("juplo.sites", String.class);
application.addInitializers(
new MultipleBeansApplicationContextInitializer(
Arrays
.stream(sites.split(","))
.map(site -> site.trim())
.toArray(size -> new String[size])));
}
}
The Bad News: Unfortunately, you have to scan all property-sources for the parameters, that you are interested in. Also, all values are represented as stings in this early startup-phase of the application-context, because Spring’s convenient conversion mechanisms are not available yet. So, you have to convert any values by yourself and stuff them in more complex data-structures as needed.
The Good News:
The property names are consistently represented in standard Java-Properties-Notation, regardless of the actual type (.properties
/ .yml
) of the property source.
Register the EnvironmentPostProcessor
Finally, you have to register the EnvironmentPostProcessor
with your Spring-Boot-App.
This is done in the META-INF/spring.factories
:
org.springframework.boot.env.EnvironmentPostProcessor=\
de.juplo.demos.multiplebeans.MultipleBeansEnvironmentPostProcessor
That’s it, your done!
Source Code
You can find the whole source code in a working mini-application on juplo.de and GitHub:
Other Blog-Posts On The Topic
- The blog-post Dynamic Beans in Spring shows a way to register beans dynamically, but does not show how to access the configuration. Also, meanwhile another interface was added to spring, that facilitates this approach:
BeanDefinitionRegistryPostProcessor
- Benjamin shows in How To Create Your Own Dynamic Bean Definitions In Spring, how this interface can be applied and how one can access the configuration. But his example only works with plain Spring in a Servlet Container
Leave a Reply