Spring dynamic beans

Posted: July 28, 2017 in Uncategorized
Tags: , , , ,

The @Bean annotation is a great way to declare Spring beans when writing code.  This allows the programmer to control which beans get instantiated and with what arguments.   Some situations call for deciding if beans should be instantiated at runtime based on configuration this is often done with the @Conditional annotation on @Configuration classes.   The @Conditional annotation works well if you are enabling or disabling a particular bean through configuration but sometimes the number of beans needs to be completely dynamic.

This often comes up if your application needs to connect to a bunch of external services such as a database.  You want the number of  services or databases that the application connects to be configurable.

For example you might want to define a connection to a dynamic number of databases by using properties in your properties file.  An  application.properties file that does this might look like the following.

 

db1.datasource.driver=org.postgresql.Driver
db1.datasource.url=jdbc:postgresql://dbhost1/dbname
db1.datasource.username=dbuser
db1.datasource.password=dbpassword

db2.datasource.driver=org.postgresql.Driver
db2.datasource.url=jdbc:postgresql://dbhost2/dbname
db2.datasource.username=dbuser
db2.datasource.password=dbpassword
...
db$n.datasource.driver=org.postgresql.Driver
db$n.datasource.url=jdbc:postgresql://dbhostn/dbname
db$n.datasource.username=dbuser
db$n.datasource.password=dbpassword

 

The application would then connect to each database instance defined in the properties file and use it.

 

You can’t create a DataSource bean in a beans.xml or with the @Bean annotation for each database connection because you don’t know how many of them there will be.

A solution to this is to create the bean definitions dynamically at runtime. I will explain how to do this with an example.

BeanDefinitionRegistryPostProcessor

The BeanDefinitionRegistryPostProcessor bean is a bean with methods that will be invoked after normal bean definitions have been added  but early enough in initialization so we can define new beans. We will implement an instance of this class to dynamically add our bean definitions.

The method that creates the BeanDefinitionRegistryPostProcessor should be static.

The createDynamicBeans method will do the actual work, I wil define this method later as a static class of the parent @Configuration class.

 

/**
 * 
 * Create a beanPostProcessor , @Bean for adding the dynamic beans.
 */
 @Bean
 static BeanDefinitionRegistryPostProcessor beanPostProcessor(final ConfigurableEnvironment environment) {
 return new BeanDefinitionRegistryPostProcessor() {

   public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException { 
   }

   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanRegistry) throws BeansException {
     createDynamicBeans(environment,beanRegistry);
   }
 
 };
}

 

We are passing an instance of a ConfigurableEnvironment down so we can use it when defining our beans.

 

The actual dynamic bean definitions are defined in my createDynamicBeans method.

static private void createDynamicBeans(ConfigurableEnvironment environment,BeanDefinitionRegistry beanRegistry) {
 Map<String,DataSourceProperties> propertyMap = parseProperties(environment);
 for(Map.Entry<String,DataSourceProperties> entry : propertyMap.entrySet()) {
   registerDynamicBean(entry.getKey(),entry.getValue(),beanRegistry);
 }
}

This method needs to

  1. Parse the properties defined in the properties file to create a DataSourceProperties class for each connection(bean instance)
  2. Create the actual bean definitions

 

Property File Parsing

The Spring @ConfigurationPropeties or @Value annotations are not particularly helpful for creating a dynamic number of objects from a set of properties of the same structure.  These annotations (particularly @ConfigurationProperties) works great when you have a single database connection but if the number of database connections is dynamic you want a dynamic number of DatasourceProperties instances.

I handle this by iterating through the properties defined in the environment and looking for properties that apply to a particular database connection/instance.  I then instantiate an instance of the DatasourceProperties class for each instance.

Walking through the list of available properties is pretty straight forward.  You just cast the available datasource properties into EnumerablePropertySource objects (if possible) and examine each property.

The convention I picked for naming my properties,  $connectionName.datasource.xxx is very useful.

I could have also moved the instance name to the property suffix (datasource.url.xxx=) this is a matter of preference.

static private Map<String,DataSourceProperties> parseProperties(ConfigurableEnvironment environment) {
 Map<String,DataSourceProperties> propertyMap = new HashMap<>(); 
 for(PropertySource source : environment.getPropertySources()) {
   if(source instanceof EnumerablePropertySource ) {
     EnumerablePropertySource propertySource = (EnumerablePropertySource) source;
     for(String property : propertySource.getPropertyNames()) {
       if(DataSourceProperties.isUrlProperty(property)) {
         String prefix = extractPrefix(property);
         propertyMap.put(prefix, new DataSourceProperties(environment,prefix));
       }
     }
   }
 }
 return propertyMap;
}

 

DatasourceProperties class

The DataSourceProperties class is an unannotated POJO that takes the environment as a constructor argument and the instance/connection name (The prefix).

 

public class DataSourceProperties {
 private static String propertyBase="datasource";
 //....
 public DataSourceProperties(ConfigurableEnvironment environment,
 String prefix) {
   this.prefix = prefix;
   this.environment = environment;
   driver = getProperty("driver");
   url = getProperty("url");
   username = getProperty("username");
   password = getProperty("password");
   primary = getProperty("primary",Boolean.class);
 }
.....

 

The getProperty method just extacts the property from the environment using the current prefix.

The complete code for the class is available in the example repository

 

Defining The Beans

The registerDynamicBeans method will register the beans for each datasource based on the properties parsed above.

The method uses the BeanDefinitionBuilder class to build a bean definition for each  dynamic bean.

We create a BasicDataSource for each database connection along with a Repository instance that uses the datasource.  The purpose of the Repository is to demonstrate how you can connect one dynamic bean to another dynamic bean.

We generate a unique name for each bean by appending the connection name to the bean name.  The bean definition will pass the values from the DataSourceProperties to the bean.  You can also pass references to other beans as demonstrated with the Repository class.

Spring-boot applications want a single JDBC datasource be declared as primary.  In applications with multiple database connections that are defined in advance the @Primary annotation is used to designated one of your DataSource beans as primary.  Dynamically defined beans can also be designated as primary  by setting the primary attribute based on the property.  If we didn’t care which datasource connection was primary we could just pick the first one and designate it as primary.

 

static private void registerDynamicBean(String prefix, DataSourceProperties dsProps,BeanDefinitionRegistry beanRegistry) { 
 logger.info("Registering beans for " + prefix);
 BeanDefinition dataSourceBeanDef = BeanDefinitionBuilder.genericBeanDefinition(BasicDataSource.class)
   .addPropertyValue("url",dsProps.getUrl())
   .addPropertyValue("username", dsProps.getUsername())
   .addPropertyValue("password", dsProps.getPassword())
   .addPropertyValue("driverClassName", dsProps.getDriver()) 
   .getBeanDefinition();
 if(dsProps.getPrimary()) {
   dataSourceBeanDef.setPrimary(true);
 }
 beanRegistry.registerBeanDefinition("datasource_" + prefix, dataSourceBeanDef);
 if(dsProps.getPrimary()) {
   beanRegistry.registerAlias("datasource_" + prefix, "dataSource");
 }
 BeanDefinition repositoryBeanDef = BeanDefinitionBuilder.genericBeanDefinition(Repository.class)
   .addConstructorArgReference("datasource_" + prefix)
   .getBeanDefinition();
 beanRegistry.registerBeanDefinition("repository_" + prefix, repositoryBeanDef);
 
}

 

These are the basic steps needed to create dynamic bean definitions.  If any of these beans, such as the Repository require other beans through the @Autowire annotation then these will be wired up as needed.

I don’t address creating a transactional manager for each of the database connections in this example.  This example is more to show how we create dynamic beans of whatever type.

In another post I plan to talk about how to create a custom bean scope for each database connection along with a custom PlatformTransactionManager for each connection.

The complete source code for this example can be found the the example repository

 

Advertisements
Comments
  1. Senthil says:

    Thanks!.. This really helps a lot.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s