Pages

Footer Pages

Spring Boot

Java String API

Java Conversions

Kotlin Programs

Kotlin Conversions

Java Threads Tutorial

Java 8 Tutorial

Monday, March 23, 2020

Spring - Dynamically register beans in 4 ways At Run-Time

1. Overview

In this tutorial, We will learn about "dynamically register bean with spring" or "dynamically add the bean to spring-context" (at run time).
This can be done without restarting the application at runtime when Loading and Removing bean in Spring Application.

If the client code needs to register objects which are not managed by Spring container, then we will need to work with an instance of BeanDefinition.

A Spring application can register a BeanDefinition by using the following method of BeanDefinitionRegistry:

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

Here, We have used the following dependencies.
  •     Spring Context 4.3.4.RELEASE: Spring Context.
  •     JDK 1.8
  •     Maven 3.3.9
Spring - Dynamically register beans (At Run-Time)

2. BeanDefinition

BeanDefinition describes a bean instance. It has setter methods that can be used to programmatically set the Spring specific characteristics to a bean, for example, BeanDefinition #setScope(String scope) can be used to set a scope other than default singleton.

3. GenericBeanDefinition

This is the commonly used BeanDefinition implementation.

It allows specifying the bean class, bean characteristics plus constructor argument values and property values.

4. Dynamically register beans


This can be done in many ways. We will be discussing the following with examples.

  • GenericBeanDefinition
  • BeanDefinitionBuilder
  • BeanFactoryPostProcessor
  • BeanDefinitionRegistryPostProcessor

4.1 Dynamic Bean Registration With GenericBeanDefinition


GenericBeanDefinition is a one-stop-shop for standard bean definition purposes. Like any bean definition, it allows for specifying a class plus optionally constructor argument values and property values. Additionally, deriving from a parent bean definition can be flexibly configured through the "parentName" property.

In general, use this GenericBeanDefinition class for the purpose of registering user-visible bean definitions (which a post-processor might operate on, potentially even reconfiguring the parent name). Use RootBeanDefinition / ChildBeanDefinition where parent/child relationships happen to be pre-determined.

Creating Example Bean class.

public class MyBean {
  private Date date;

  public void doSomething () {
      System.out.println("from my bean, date: " + date);
  }

  public void setDate (Date date) {
      this.date = date;
  }
}

Registering the above, created bean dynamically using GenericBeanDefinition.

public class GenericBeanDefinitionExample {

  public static void main (String[] args) {
      DefaultListableBeanFactory context =
                new DefaultListableBeanFactory();

      GenericBeanDefinition gbd = new GenericBeanDefinition();
      gbd.setBeanClass(MyBean.class);

      MutablePropertyValues mpv = new MutablePropertyValues();
      mpv.add("date", new Date());

      //alternatively we can use:
      // gbd.getPropertyValues().addPropertyValue("date", new Date());
      gbd.setPropertyValues(mpv);

      context.registerBeanDefinition("myBeanName", gbd);

      MyBean bean = context.getBean(MyBean.class);
      bean.doSomething();
  }
}

Output:

from my bean, date: Wed Jult 23 12:20:58 EDT 2019

4.2 Dynamic Bean Registration With BeanDefinitionBuilder

Programmatic means of constructing BeanDefinitions using the builder pattern. Intended primarily for use when implementing Spring 2.0 NamespaceHandlers. The only difference here is, BeanDefinitionBuilder uses Builder Pattern.


Creating another bean class.
public class MyBean {
  private String str;

  public void setStr (String str) {
      this.str = str;
  }

  public void doSomething () {
      System.out.println("from MyBean " + str);
  }
}


Example to register the bean dynamically using BeanDefinitionBuilder.

public class BeanDefinitionBuilderExample {

  public static void main (String[] args) {
      DefaultListableBeanFactory beanFactory =
                new DefaultListableBeanFactory();

      BeanDefinitionBuilder b =
                BeanDefinitionBuilder.rootBeanDefinition(MyBean.class)
                                     .addPropertyValue("str", "myStringValue");

      beanFactory.registerBeanDefinition("myBean", b.getBeanDefinition());


      MyBean bean = beanFactory.getBean(MyBean.class);
      bean.doSomething();
  }
}

Output:

from MyBean myStringValue

Injecting other bean references using BeanDefinitionBuilder

Creating Bean 1

public class Bean1 {
  private Bean2 otherBean;

  public void setOtherBean (Bean2 otherBean) {
      this.otherBean = otherBean;
  }

  public void doSomething () {
      otherBean.doSomething();
  }
}

Creating Bean 2

public class Bean2 {

  public void doSomething () {
      System.out.println("from other bean ");
  }
}

Setting the Bean2 in Bean1


public class InjectingOtherBeans {

  public static void main (String[] args) {
      DefaultListableBeanFactory context =
                new DefaultListableBeanFactory();

      //define and register MyOtherBean
      GenericBeanDefinition beanOtherDef = new GenericBeanDefinition();
      beanOtherDef.setBeanClass(Bean2.class);
      context.registerBeanDefinition("other", beanOtherDef);

      //definine and register myBean
      GenericBeanDefinition beanDef = new GenericBeanDefinition();
      beanDef.setBeanClass(Bean1.class);
      MutablePropertyValues mpv = new MutablePropertyValues();
      mpv.addPropertyValue("otherBean", context.getBean("other"));
      beanDef.setPropertyValues(mpv);
      context.registerBeanDefinition("myBean", beanDef);

      //using MyBean instance
      MyBean bean = context.getBean(MyBean.class);
      bean.doSomething();
  }
}

Output:

from other bean 


4.3 Dynamic Bean Registration With BeanFactoryPostProcessor


A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Allows for custom modification of an application context's bean definitions, adapting the bean property values of the context's underlying bean factory. Application contexts can auto-detect BeanFactoryPostProcessor beans in their bean definitions and apply them before any other beans get created.

Creating config.

@Configuration
public class MyConfig {
  @Bean
  MyConfigBean myConfigBean () {
      return new MyConfigBean();
  }
}

BeanFactoryPostProcessor allows client code to customize bean definitions. The method BeanFactoryPostProcessor.postProcessBeanFactory is called by the Spring startup process just after all bean definitions have been loaded, but no beans have been instantiated yet.

public class MyConfigBean implements BeanFactoryPostProcessor {

  @Override
  public void postProcessBeanFactory (
            ConfigurableListableBeanFactory beanFactory)
            throws BeansException {

      GenericBeanDefinition bd = new GenericBeanDefinition();
      bd.setBeanClass(MyBean.class);
      bd.getPropertyValues().add("strProp", "my string property");

      ((DefaultListableBeanFactory) beanFactory)
                .registerBeanDefinition("myBeanName", bd);
  }
}

Creating another Bean for Demo.


public class MyBean {
  private String strProp;

  public void setStrProp (String strProp) {
      this.strProp = strProp;
  }

  public void doSomething () {
      System.out.println("from MyBean:  " + strProp);
  }
}

Main class for BeanFactoryPostProcessor Example.

public class BeanFactoryPostProcessorExample {

  public static void main (String[] args) {
      AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MyConfig.class);
      MyBean bean = context.getBean(MyBean.class);
      bean.doSomething();
  }
}

Output:

from MyBean:  my string property
WARNING: @Bean method MyConfig.myConfigBean is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.

4.4 Dynamic Bean Registration With BeanDefinitionRegistryPostProcessor


Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in. In particular, BeanDefinitionRegistryPostProcessor may register further bean definitions which in turn define BeanFactoryPostProcessor instances.

Creating another config class.

@Configuration
public class MyConfig {
  @Bean
  MyConfigBean myConfigBean () {
      return new MyConfigBean();
  }
}

This is a sub-interface of BeanFactoryPostProcessor (last example). It allows for the registration of bean definitions. It's method postProcessBeanDefinitionRegistry is called before BeanFactoryPostProcessor#postProcessBeanFactory. This interface is more focused on the BeanDefinition registration rather than general-purpose BeanFactoryPostProcessor.


Creating implementation class for BeanDefinitionRegistryPostProcessor.

public class MyConfigBean implements BeanDefinitionRegistryPostProcessor {

  @Override
  public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry registry)
            throws BeansException {

      GenericBeanDefinition bd = new GenericBeanDefinition();
      bd.setBeanClass(MyBean.class);
      bd.getPropertyValues().add("strProp", "my string property");
      registry.registerBeanDefinition("myBeanName", bd);
  }

  @Override
  public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
      //no op
  }
}

Creating a brand new Bean class.

public class MyBean {
  private String strProp;

  public void setStrProp (String strProp) {
      this.strProp = strProp;
  }

  public void doSomething () {
      System.out.println("from MyBean:  " + strProp);
  }
}

Main class for BeanDefinitionRegistryPostProcessor Example.

public class BeanDefinitionRegistryPostProcessorExample {

  public static void main (String[] args) {
      AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MyConfig.class);

      MyBean bean = (MyBean) context.getBean("myBeanName");
      bean.doSomething();
  }
}

Output:

from MyBean:  my string property
WARNING: Cannot enhance @Configuration bean definition 'beanDefinitionRegistryPostProcessorExample.MyConfig' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.

5. Unregistering the Bean at run time


If we need to remove registered beans at runtime, we can do the same as below.

To remove or unregister the bean from spring context.

beanRegistry.removeBeanDefinition("bean") 

To delete/clear the singleton bean from context.

beanRegistry.destroySingleton("bean")

6. Conclusion


In this article, We have seen how to register a bean dynamically in the Spring framework.

Shown example programs on GenericBeanDefinition, BeanDefinitionBuilder, BeanFactoryPostProcessor and BeanDefinitionRegistryPostProcessor.

References

stackoverflow
LogicBig

2 comments:

Please do not add any spam links in the comments section.