Spring allows us to use beans for a lot of operations (and this is the most common way of expressing our object-oriented thinking). We can decide whether they are the only instance (singleton or prototype) in the container. Through the bean factory post processor we can also 初始化
perform some additional operations.
In this article, let's introduce the bean factory post processor . In the first part, we discover the mystery behind this concept. Later we will write some interesting code to let everyone understand the concept better.
What are post processors for Spring factories?
Sometimes we may need to implement some dynamic behavior in Spring application. As a simple example, suppose in your website, you want to display two text content by time. In the morning, you will display "Good Morning". In the afternoon, the text displayed will be "Good afternoon". Also, you have two daily deployments, one at 12am and another at 12pm. It should be emphasized that this text content must be handled by a bean. We now have two options: change the application context file each time we deploy (too cumbersome), or define a bean that implements the org.springframework.beans.factory.config.BeanFactoryPostProcessor interface. The second solution is more elegant, because we only need to write the code once, and then we can ignore its existence (no need to modify it again and again).
So, where is this graceful BeanFactoryPostProcessor
beauty? It is an interface implemented by beans that can modify the definitions of other beans. Note that only definitions can be modified, i.e. constructor parameters, property values. BeanFactoryPostProcessor
The bean is called before the "normal" bean is initialized, which is why it can modify the metadata (meta data). The call is implemented through the protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) of org.springframework.context.support.AbstractApplicationContext :
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}
Inside PostProcessorRegistrationDelegate
, the method responsible for the execution of the bean factory post processor is:
/**
* Invoke the given BeanFactoryPostProcessor beans.
*/
private static void invokeBeanFactoryPostProcessors(
Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
for (BeanFactoryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanFactory(beanFactory);
}
}
As you can see, BeanFactoryPostProcessor
the main method is overridden by the implementation postProcessBeanFactory
. This is also where we come to customize the bean definition ourselves. We do this by customizing on the org.springframework.beans.factory.config.BeanDefinition object. I've written about this in Spring 5 Source Parsing - Singletons and Prototype Beans in the Spring Framework, which ( BeanDefinition对象
) contain a lot of information about bean metadata: constructor parameters, property values or scope.
A simple Spring bean factory post processor Demo
Important parts of the theory have been described above. In this part, we focus on a simple and practical case. Do you remember the "Good morning" and "Good afternoon" examples from Part 1? If you forget, please go back and look again. Next, let's try to implement this case in code. First, we'll define some beans in the configuration file:
<bean class="com.migo.bean.BeanModifier">
<bean name="welcomerBean" class="com.migo.bean.Welcomer" init-method="initWelcomer">
<property name="welcomeText" value="Good morning"></property>
</bean>
</bean>
The first bean represents the bean that will implement BeanFactoryPostProcessor
the interface. The second bean is the injected class that displays the welcome text on the page. They are the code for two beans:
// Welcomer.java
public class Welcomer {
private String welcomeText;
public void initWelcomer() {
LOGGER.debug("Welcomer is initialized");
}
public void setWelcomeText(String welcomeText) {
LOGGER.debug("Setting welcomeText to: "+welcomeText);
this.welcomeText = welcomeText;
}
public String getWelcomeText() {
return this.welcomeText;
}
@Override
public String toString() {
return "Welcomer {text: "+this.welcomeText+"}";
}
}
// BeanModifier.java
public class BeanModifier implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Calendar calendar = Calendar.getInstance();
if (calendar.get(Calendar.AM_PM) == Calendar.PM) {
BeanDefinition welcomerDef = beanFactory.getBeanDefinition("welcomerBean");
welcomerDef.getPropertyValues().add("welcomeText", "Good afternoon");
}
} catch (Exception e) {
LOGGER.error("An error occurred on setting welcomeText", e);
}
}
}
// test method
ApplicationContext context = new FileSystemXmlApplicationContext("/home/bartosz/webapp/src/main/resources/META-INF/applicationContext.xml");
Welcomer welcomer = (Welcomer) context.getBean("welcomer");
System.out.println("Text: "+welcomer.getWelcomeText());
If it is afternoon, the output should be:
Setting welcomeText to: Good afternoon
Welcomer is initialized
Text: Good afternoon
We can see that is called before the actual initialization of BeanModifier
. Welcomer
Since the overriding overrides the postProcessBeanFactory
method, we can check the date and welcomeText
set the correct value for the property.
This article is short, but it describes some practical operations how we can implement it in a more efficient way in some "dynamic" scenarios. For example, you will encounter this. We often see that a game will have routine maintenance, then we will find that the leaderboard will be refreshed after the routine maintenance, and every time you log in to the game, some of your attributes or points will be updated. Refresh, in fact, every time you log in, you initialize your bean again, so we can do a lot of things, such as adding some bonus points for the best users. With BeanFactoryPostProcessor
this bean, this processing can be done automatically within the Java method, instead of having to do it manually every time we deploy.