Enterprise Java

Dynamic Bean Registration Based on Properties

Spring offers a powerful approach to configuring applications: defining beans through annotations and properties. But what if we need to create beans dynamically based on configuration loaded at runtime? In this article, we’ll explore how to achieve dynamic bean registration using the BeanDefinitionRegistryPostProcessor interface in a Spring Boot application.

1. Understanding Dynamic Bean Registration

Traditionally, beans in a Spring application are defined statically within XML configuration files or through Java-based configuration classes using annotations like @Component, @Service, or @Repository. However, there are scenarios where static bean registration might not suffice. For instance, when dealing with a large number of similar beans or when the configuration of beans needs to be determined dynamically at runtime based on certain conditions.

Dynamic bean registration solves these challenges by allowing beans to be created and registered programmatically, based on custom properties or conditions defined within the application.

1.1 BeanDefinitionRegistryPostProcessor

The BeanDefinitionRegistryPostProcessor interface in Spring Boot provides a way to modify bean definitions before they are registered with the application context. It allows for dynamic manipulation of bean definitions, making it an ideal candidate for dynamically registering beans based on custom properties.

2. Code Example: Dynamic Bean Registration with API Client

This section will demonstrate the dynamic bean registration process with a simplified example using Spring Boot. We will integrate an API client bean that communicates with an external API and register it dynamically based on custom properties.

2.1 Api Client Class

public class ApiClient {

    private String baseUrl;
    private String apiKey;

    public ApiClient(String baseUrl, String apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
    }

    public ApiClient() {
    }

    public void showDetails() {
        System.out.println("ApiClient with key: " + apiKey + " located at " + baseUrl);
    }

    public String getBaseUrl() {
        return baseUrl;
    }

    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }
    
}

2.2 Custom Properties

Create a properties file application.yml with entries defining the APIs

## YAML Template.
---
api:
  clients:
    - apiKey: 1111
      baseUrl: https://api.javacodegeeks.com/v1
    - apiKey: 2222
      baseUrl: https://examples.javacodegeeks.com/v1

The above YAML snippet describes an API configuration with multiple clients, each with its base URL (baseUrl) and API key (apiKey). The first client configuration defines a client with a base URL of http://api.javacodegeeks.com/v1 and an API key of 1111 and the second client configuration represents another client with a base URL of http://examples.javacodegeeks.com/v1 and an API key of 2222.

2.3 Dynamic Registration Class

Next, we will create a configuration class responsible for registering the ApiClient class. Below is the class that dynamically registers ApiClient beans based on the properties defined in the environment loaded from the application.yml file. Each ApiClient bean is registered with a bean name derived from its API key.

public class AppConfig implements BeanDefinitionRegistryPostProcessor {

    List<ApiClient> clients;

    public AppConfig(Environment environment) {
        Binder binder = Binder.get(environment);
        clients = binder.bind("api.clients", Bindable.listOf(ApiClient.class)).get();
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        clients.forEach(client -> {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ApiClient.class);
            builder.addPropertyValue("apiKey", client.getApiKey());
            builder.addPropertyValue("baseUrl", client.getBaseUrl());
            registry.registerBeanDefinition("apiclient_" + client.getApiKey(), builder.getBeanDefinition());
        });
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // Empty implementation, not required for this example
    }

}

This block of code defines a Spring @Configuration class named AppConfig that implements the BeanDefinitionRegistryPostProcessor interface. In the class, the constructor takes an Environment object as a parameter. Inside the constructor, a Binder is created using Binder.get(environment). Using the Binder, the api.clients property is bound to a list of ApiClient objects. This list is stored in the clients field.

The postProcessBeanDefinitionRegistry() method is called by the Spring container during initialization after the bean definitions are registered but before the beans are instantiated. It iterates over the clients list obtained from the environment and for each ApiClient in the list, a BeanDefinition is created using BeanDefinitionBuilder.genericBeanDefinition(ApiClient.class).

The properties of the ApiClient object (apiKey and baseUrl) are set on the bean definition using builder.addPropertyValue() and finally, the bean definition is registered with the bean registry using registry.registerBeanDefinition().

2.4 Configuration

Include AppConfig in the main Spring configuration class.

@SpringBootApplication
public class DynamicbeansregistrationApplication {

    public static void main(String[] args) {
        // SpringApplication.run(DynamicbeansregistrationApplication.class, args);
        ApplicationContext context = SpringApplication.run(DynamicbeansregistrationApplication.class, args);
        ApiClient bean = (ApiClient) context.getBean("apiclient_2222");
        bean.showDetails();
   
    }

    @Bean
    public AppConfig appConfiguration(ConfigurableEnvironment environment) {
        return new AppConfig(environment);
    }

}

Above, we define a bean method named appConfiguration that returns an instance of AppConfig. This tells Spring to include this bean in the application context. The ConfigurableEnvironment object is injected into the method as a parameter, which allows the environment properties to be passed to the AppConfig constructor.

In the main method, The ApiClient bean with the name apiclient_2222 is retrieved from the context using context.getBean() and the showDetails() method of the ApiClient bean is invoked to display its details. The output is:

Fig 1. Output from running the application on dynamic beans registration with custom properties
Fig 1. Output from running the application on dynamic beans registration with custom properties

3. Testing Dynamic Bean Registration

We write integration tests to test the dynamic bean registration process to ensure that the API client bean is registered correctly based on the provided custom properties. Below is a simple test class using JUnit and Spring Boot’s testing utilities.

@SpringBootTest
class DynamicbeansregistrationApplicationTests {

    @Autowired
    private ApplicationContext context;

    @Test
    void testApiClientBeanRegistration() {
        ApiClient apiClient = (ApiClient) context.getBean("apiclient_2222");
        assertNotNull(apiClient);
        assertEquals("https://examples.javacodegeeks.com/v1", apiClient.getBaseUrl());

        ApiClient anotherClient = (ApiClient) context.getBean("apiclient_1111");
        assertNotNull(apiClient);
        assertEquals("https://api.javacodegeeks.com/v1", anotherClient.getBaseUrl());
    }

}

The above test class verifies that the ApiClient beans are correctly registered in the application context with their respective properties (baseUrl).

4. Conclusion

In this article, we explored the dynamic registration of beans in a Spring Boot application based on custom properties. We delved into how the BeanDefinitionRegistryPostProcessor interface can be leveraged to achieve this dynamic bean registration, allowing for greater flexibility in application configuration. In conclusion, dynamic bean registration based on custom properties offers a powerful mechanism for configuring and managing beans in a Spring application. We can build more adaptable and configurable applications by decoupling bean definitions from the source code and allowing them to be defined dynamically.

5. Download the Source Code

This was an article on dynamic registration of Spring beans using custom properties.

Download
You can download the full source code of this example here: spring beans dynamic registration properties

Omozegie Aziegbe

Omos holds a Master degree in Information Engineering with Network Management from the Robert Gordon University, Aberdeen. Omos is currently a freelance web/application developer who is currently focused on developing Java enterprise applications with the Jakarta EE framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button