Spring5源码解析-Spring中的bean工厂后置处理器

Spring5源码解析-Spring中的bean工厂后置处理器

Spring允许我们使用bean来进行大量的操作(这也是我们面向对象思想最常用的表达方式)。我们可以决定它们在容器中是否是(单例或者是原型)唯一实例。通过bean工厂后置处理器我们还可以在初始化时进行一些额外的操作。

在这篇文章中,来介绍下bean factory post processor。在第一部分,我们来发现下这个概念背后玄机。之后我们会写一些有趣代码来让大伙更好地理解这个概念。

什么是Spring工厂的后置处理器?

有时我们可能需要在Spring应用程序中实现一些动态行为。举个简单的例子,假设在你的网站中,你要显示按时间来显示两个文本内容。上午的时候,你会显示“早安”。下午,显示的文字将是“下午好”。另外,你有两个日常部署,上午12点,另一个在晚上12点。需要强调的是,这个文本内容必须由一个bean来处理。我们现在有两个选择:每次部署时更改应用程序上下文文件(太麻烦了),或者定义一个实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口的bean 。第二个解决方案更优雅,因为我们只需要编写一次代码,然后就可以忽视它的存在了(无须次次去修改了)。

那么,这个优雅的BeanFactoryPostProcessor是何方神圣?它是由bean实现的接口,它们可以修改其他bean的定义。请注意,只能修改定义,即构造函数参数,属性值。BeanFactoryPostProcessorbean在初始化“正常”bean之前被调用,这就是为什么它能修改元数据的原因(meta data)。调用是通过org.springframework.context.support.AbstractApplicationContext的 protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)实现的:

1
2
3
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}

PostProcessorRegistrationDelegate里面,负责bean factory后置处理器执行的方法是:

1
2
3
4
5
6
7
8
9
/**
* Invoke the given BeanFactoryPostProcessor beans.
*/
private static void invokeBeanFactoryPostProcessors(
Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
for (BeanFactoryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanFactory(beanFactory);
}
}

如你所见,由BeanFactoryPostProcessor实现覆盖的主要方法是postProcessBeanFactory。这也是我们来自己定制bean定义的地方。我们通过在org.springframework.beans.factory.config.BeanDefinition对象上进行定制。关于这点我已经在Spring5源码解析-Spring框架中的单例和原型bean的文章中已经写过,它们(BeanDefinition对象)包含大量关于bean元数据的信息:构造函数参数,属性值或作用域。

一个简单的Spring bean厂后置处理器Demo

关于理论的重要部分已经都在前面进行了描述。在这部分中,我们将重点放在一个简单实用的案例上。你还记得第一部分的“早安”和“下午好”的例子?如果忘了,请回去再瞅眼的。接着,让我们尝试在代码中实现这种案例。首先,我们将在配置文件中定义一些bean:

1
2
3
4
5
6
<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>

第一个bean代表将实现BeanFactoryPostProcessor接口的bean。第二个bean是注入的类,它会在页面中显示欢迎的文本内容。他们是两个bean的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 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());

如果现在是下午,输出应该是:

1
2
3
Setting welcomeText to: Good afternoon
Welcomer is initialized
Text: Good afternoon

我们可以看到,BeanModifierWelcomer的真正初始化之前被调用。由于重写覆盖了postProcessBeanFactory方法,这样我们可以通过检查日期,并为属性welcomeText设置正确的值。

这篇文章虽短,但它描述了一些在一些“动态”场景中我们如何以一个更效率的方式来实现的实用操作。比如,你会碰到这种,我们常常见到一个游戏会有例行维护,那么我们会发现排行榜会在例行维护后刷新,你每次登录游戏也会对你的一些属性或者积分进行刷新,其实你每次登录就是又初始化了一遍你这个bean,这样,我们就可以做很多事情了,比如为最佳用户添加一些奖励积分。通过BeanFactoryPostProcessor这个 bean,这种处理就可以在Java方法内自动完成,无须我们在每次部署时通过手动来完成。

您的支持将鼓励我继续创作!