In this post under Spring Core, I will explain with example the purpose of “BeanFactoryPostProcessor” Spring class.
We define our Spring beans and give it to Spring container to create instances of those beans.
Spring container reads the bean definitions and create instances of beans.
What if we want to change the definition of those Spring beans at runtime.
That is where “BeanFactoryPostProcessor” Spring class comes into picture.
“BeanFactoryPostProcessor” comes after Spring reads the bean definitions and before creating any instances of those beans.
At this point we can change the bean definitions.
Lets see an example. For our example we create two calculator instances “GeneralCalculator” and “ScientificCalculator”.
GeneralCalculator
package core.package54;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component("generalCalculator")
@Primary
public class GeneralCalculator implements ICalculator {
@Override
public String toString() {
return "GeneralCalculator";
}
}
ScientificCalculator
package core.package54;
import org.springframework.stereotype.Component;
@Component("scientificCalculator")
public class ScientificCalculator implements ICalculator {
@Override
public String toString() {
return "ScientificCalculator";
}
}
The “GeneralCalculator” is marked with “@Primary” annotation.
Both implements “ICalculator” interface. Below is the structure of the interface.
ICalculator
package core.package54;
public interface ICalculator {
}
Next we will create “MathManager” class with below structure.
MathManager
package core.package54;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("mathManager")
public class MathManager {
@Autowired
private ICalculator iCalculator;
public ICalculator getCalculator() {
return iCalculator;
}
}
Now when Spring creates an instance of “MathManager” it injects an instance of class that implements “ICalculator” interface.
In our case it is either “GeneralCalculator” or “ScientificCalculator” but since “GeneralCalculator” is marked with “@Primary” annotation, Spring prefers this always.
We can change this i.e., mark “ScientificCalculator” with “@Primary” annotation at runtime and remove “@Primary” annotation from “GeneralCalculator”.
So that when Spring plans to inject instances of class that implements “ICalculator” interface, it prefers “ScientificCalculator” instead of “GeneralCalculator”.
Below is the code that does the above.
MyBeanFactoryPostProcessor
package core.package54;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("scientificCalculator");
beanDefinition.setPrimary(true);
beanDefinition = beanFactory.getBeanDefinition("generalCalculator");
beanDefinition.setPrimary(false);
}
}
In the above code, at line 13, I get the bean definition of “ScientificCalculator” and set it as primary at line 14.
At line 15, I get the bean definition of “GenericCalculator” and set it as non-primary at line 16.
So as a result we have changed the bean definition of both “ScientificCalculator” and “GeneralCalculator” at runtime.
Below is the main class for your reference
Main class
package core.package54;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "core.package54")
public class Example54 {
@Autowired
private MathManager mathManager;
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Example54.class);
MathManager mathManager = applicationContext.getBean(MathManager.class);
ICalculator iCalculator = mathManager.getCalculator();
System.out.println(iCalculator);
}
}
Below is the output
Output
ScientificCalculator
In this way we can change the definition of beans at runtime.