转载

Spring 源码学习(六)扩展功能 上篇-BeanFactoryPostProcessor

结束了前面的基础结构分析,了解到 Spring 是如何识别配置文件和进行解析属性,最终将 bean 加载到内存中。 同时为了更好得理解 Spring 的扩展功能,我们先来巩固一下 beanFactory 和 bean 的概念,然后再分析新内容后处理器 PostProcessor 。

本篇阅读思路:

  1. 是什么
  2. 如何使用
  3. Spring 实现逻辑

Table of Contents generated with DocToc

  • BeanFactoryPostProcessor

前言

首先我们先将 Spring 想像成一个大容器,然后保存了很多 bean 的信息,根据定义: bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象 ,也可以简单得理解为我们在配置文件配置好元数据, Spring IoC 容器会帮我们对 bean 进行管理,这些对象在使用的时候通过 Spring 取出就能使用。

那么是谁帮这个 Spring 管理呢,那就是 BeanFactory ,粗暴点直译为 bean 工厂,但其实它才是承担容器功能的幕后实现者,它是一个接口,提供了获取 bean 、获取别名 Alias 、判断单例、类型是否匹配、是否原型等方法定义,所以需要通过引用,实现具体方法才后才能使用。

回顾完 beanFactory 后,我们再来回顾在前面内容中,看到过很多后处理器 PostProcessor 的代码影子,分别是 BeanFactoryPostProcessor :主体是 BeanFactory , 和 BeanPostProcessor :主体是 Bean , 这两者都是 Spring 用来为使用者提供的扩展功能之一。

接下来为了更好的分析和了解使用后处理器,实现扩展功能,一起跟踪源码学习吧~

BeanFactoryPostProcessor

是什么

BeanFactoryPostProcessor 是一个接口,在里面只有一个方法定义:

@FunctionalInterface
public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

定义:是 Spring 对外提供可扩展的接口,能够在容器加载了所有 bean 信息( AbstractApplicationContext#obtainFreshBeanFactory 方法)之后, bean 实例化之前执行,用来修改 bean 的定义属性。

可以看到,方法参数是 ConfigurableListableBeanFactory beanFactory ,说明我们可以通过引用该接口,在方法中实现逻辑,对容器中 bean 的定义(配置元数据)进行处理

同时,执行后处理器是有先后顺序的概念,我们可以通过设置 order 属性来控制它们的执行次序,前提是 BeanFactoryPostProcessor 实现了 Order 接口。

下面一起来看下它的如何使用,以及是如何进行注册和执行~

如何使用

官方例子:PropertyPlaceholderConfigurer

这个类是 Spring 容器里自带的后处理器,是用来替换占位符,填充属性到 bean 中。

像我们在 xml 文件中配置了属性值为 ${max.threads} ,能够通过它来找到 max.threads 在配置文件对应的值,然后将属性填充到 bean 中。

虽然在 Spring 5 中, PropertyPlaceholderConfigurer 已经打上了不建议使用的标志 @Deprecated ,看了文件注释,提示我们去使用 Environment 来设置属性,但我觉得这个后处理器的思想是一样的,所以还是拿它作为例子进行熟悉。

先来看下它的继承体系:

Spring 源码学习(六)扩展功能 上篇-BeanFactoryPostProcessor

忽略它被冷落的下划线标签

当 Spring 加载任何实现了 BeanFactoryPostProcessor 接口的 bean 配置时, 都会在 bean 工厂载入所有 bean 的配置之后执行 postProcessBeanFactory 方法 。

可以看到它引用了 BeanFactoryPostProcessor 接口,在 PropertyResourceConfigurer 父类中实现了 postProcessBeanFactory ,在方法中依次调用了合并资源 mergedProps 方法,属性转换 convertProperties 方法和 真正修改 beanFactory 中配置元数据的 processProperties(beanFactory, mergedProps) 方法 。

因为通过在 PropertyPlaceholderConfigurer 的后处理方法 postProcessBeanFactory , BeanFactory 在实例化任何 bean 之前获得配置信息, 从而能够正确解析 bean 描述文件中的变量引用 。

所以通过后处理器,我们能够对 beanFactory 中的 bean 配置信息在实例化前还有机会进行修改。

题外话:想到之前我遇到全半角空格的配置问题,程序认为全半角空格不是同一个字符,但肉眼却很难察觉,所以感觉可以在加载配置信息时,通过自定义一个后处理,在实例化之前,将全角空格转成半角空格,这样程序比较时都变成统一样式。 所以后处理器提供的扩展功能可以让我们对想要处理的 bean 配置信息进行特定修改

使用自定义 BeanFactoryPostProcessor

实现的功能与书中的类似,例如之前西安奔驰汽车维权事件,如果相关网站想要屏蔽这奔驰这两个字,可以通过后处理器进行替换:

1. 配置文件 factory-post-processor.xml

<bean id="carPostProcessor" class="context.CarBeanFactoryPostProcessor">
	<property name="obscenties">
		<!--set 属性-->
		<set>
			<value>奔驰</value>
			<value>特斯拉</value>
		</set>
	</property>
</bean>

<bean id="car" class="base.factory.bean.Car">
	<property name="price" value="10000"/>
	<property name="brand" value="奔驰"/>
</bean>

2. 后处理器 CarBeanFactoryPostProcessor

public class CarBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

	/**
	 * 敏感词
	 */
	private Set<String> obscenties;

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		// 从 beanFactory 中获取 bean 名字列表
		String[] beanNames = beanFactory.getBeanDefinitionNames();
		for (String beanName : beanNames) {
			BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
			StringValueResolver valueResolver = strVal -> {
				if (isObscene(strVal)) return "*****";
				return strVal;
			};
			BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
			// 这一步才是真正处理 bean 的配置信息
			visitor.visitBeanDefinition(definition);
		}

	}

	/**
	 * 判断 value 是否在敏感词列表中
	 * @param value	值
	 * @return		boolean
	 */
	private boolean isObscene(Object value) {
		String potentialObscenity = value.toString().toUpperCase();
		return this.obscenties.contains(potentialObscenity);
	}
}

3. 启动

public class BeanFactoryPostProcessorBootstrap {

	public static void main(String[] args) {
	    ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/factory-post-processor.xml");
	    // 这两行其实可以不写,因为在 refresh() 方法中,调用了一个函数将后处理器执行了,具体请往下看~
	    BeanFactoryPostProcessor beanFactoryPostProcessor = (BeanFactoryPostProcessor) context.getBean("carPostProcessor");
	    beanFactoryPostProcessor.postProcessBeanFactory(context.getBeanFactory());
	    // 输出 :Car{maxSpeed=0, brand='*****', price=10000.0},敏感词被替换了
	    System.out.println(context.getBean("car"));
    }
}

通过上面的演示代码,新增一个自定义实现 BeanFactoryPostProcessor 的后处理器 CarBeanFactoryPostProcessor ,在 postProcessBeanFactory 方法中进行逻辑处理,最后通过 visitor.visitBeanDefinition 修改配置信息。

查看输出结果,能发现宝马敏感词已经被屏蔽了,实现了后处理器的逻辑功能~

在哪注册

按照一般套路,后处理器需要有个地方进行注册,然后才能进行执行,通过代码分析,的确在 AbstractApplicationContext 中看到了 beanFactoryPostProcessors 数组列表,但往数组中添加后处理器的方法 addBeanFactoryPostProcessor 只在单元测试包调用了。

这让我很迷惑它到底是在哪里进行注册,直到我看到它的执行方法,原来我们定义的后处理器在 bean 信息加载时就放入注册表中,然后通过 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false) 方法获取后处理器列表遍历执行。

所以前面的 beanFactoryPostProcessors 数组列表,是让我们通过硬编码方法方式,手动添加进去,然后通过 context.refresh() 方法后,再执行硬编码的后处理器

例如下面这个例子

public class HardCodeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("Hard Code BeanFactory Post Processor execute time");
	}
}

// 硬编码 后处理器执行时间
BeanFactoryPostProcessor hardCodeBeanFactoryPostProcessor = new HardCodeBeanFactoryPostProcessor();
context.addBeanFactoryPostProcessor(hardCodeBeanFactoryPostProcessor);
// 更新上下文
context.refresh();
// 输出:
//Hard Code BeanFactory Post Processor execute time
//Car{maxSpeed=0, brand='*****', price=10000.0}
System.out.println(context.getBean("car"));

激活 BeanFactoryPostProcessor

看完了怎么使用后,我们来分析下 Spring 是如何识别和执行 BeanFactoryPostProcessor ,入口方法:

org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

实际上,委派了 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()) 代理进行执行。由于代码有点长,所以我画了一个流程图,可以结合流程图来分析代码:

流程图

Spring 源码学习(六)扩展功能 上篇-BeanFactoryPostProcessor

代码

public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
	Set<String> processedBeans = new HashSet<>();
	// beanFactory 默认使用的是 DefaultListableBeanFactory,属于 BeanDefinitionRegistry
	if (beanFactory instanceof BeanDefinitionRegistry) {
		BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
		// 两个后处理器列表
		List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
		List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
		// 硬编码注册的后处理器
		for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
			// 分类处理
		}
		List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
		// 首先,调用实现 priorityOrder 的 beanDefinition 这就是前面提到过的优先级概念,这一步跟下面的优先级不一样之处,这一步的优先级是带有权重
		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
		for (String ppName : postProcessorNames) {
			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
				processedBeans.add(ppName);
			}
		}
		// 对后处理器进行排序
		sortPostProcessors(currentRegistryProcessors, beanFactory);
		registryProcessors.addAll(currentRegistryProcessors);
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
		currentRegistryProcessors.clear();
		// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
		postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
		for (String ppName : postProcessorNames) {
			if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
				currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
				processedBeans.add(ppName);
			}
		}
		sortPostProcessors(currentRegistryProcessors, beanFactory);
		registryProcessors.addAll(currentRegistryProcessors);
		// 执行 definitionRegistryPostProcessor 接口的方法 :postProcessBeanDefinitionRegistry
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
		currentRegistryProcessors.clear();
		// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.最后,调用所有其他后处理器,直到不再出现其他 bean 为止
		boolean reiterate = true;
		while (reiterate) {
			reiterate = false;
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				if (!processedBeans.contains(ppName)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
					reiterate = true;
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();
		}
		// 执行 postProcessBeanFactory 回调方法
		invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
	}
	else {
		invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
	}
	// 不要在这里初始化 factoryBean:我们需要保留所有常规 bean 未初始化,以便让 bean 工厂后处理程序应用于它们
	// 注释 6.4 在这个步骤中,我们自定义的 carBeanFactoryPostProcessor 才真正注册并执行
	String[] postProcessorNames =
			beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);.
    // 跳过分类的逻辑
	// 首先执行的是带有权重顺序的后处理器
	sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
	invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
	// 下一步执行普通顺序的后处理器
	List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
	for (String postProcessorName : orderedPostProcessorNames) {
		orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
	}
	sortPostProcessors(orderedPostProcessors, beanFactory);
	invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
	// 最后执行的是普通的后处理器
	List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
	for (String postProcessorName : nonOrderedPostProcessorNames) {
		nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
	}
	invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
	// 清除缓存的合并bean定义,因为后处理程序可能已经修改了原始元数据,例如替换值中的占位符
	beanFactory.clearMetadataCache();
}

从上面贴的代码中能够看到,对于 beanFactoryPostProcessor 的处理主要分两种情况:

  • beanFactory 是 BeanDefinitionRegistry 类型 :需要特殊处理
  • beanFactory 不是 BeanDefinitionRegistry 类型 :进行普通处理

对于每种情况都需要考虑硬编码注入注册的后处理器(上面已经提到如何进行硬编码)以及通过配置注入的后处理器

对于 BeanDefinitionRegistry 类型的处理器的处理主要包括以下内容:

  1. 处理硬编码注册的后处理器
  2. 记录后处理器主要使用以下三个 List
    • registryPostProcessors :记录通过 硬编码 方式注册的 BeanDefinitionRegistryPostProcessor
    • regularPostProcessors :记录通过 硬编码 方式注册的 BeanFactoryPostProcessor
    • regitstryPostProcessorBeans :记录通过 配置 方式注册的 BeanDefinitionRegistryPostProcessor
  3. 对于以上后处理器列表,统一调用 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法
  4. 对 beanFactoryPostProcessors 中非 BeanDefinitionRegistryPostProcessor 类型的后处理器进行统一的 postProcessBeanFactory 方法
  5. 普通 beanFactory 处理:其实在这一步中,就是忽略了 BeanDefinitionRegistryPostProcessor 类型,对 BeanFactoryPostProcessor 进行直接处理。

流程图中描述了整体调用链路,具体调用方法在代码中的注释也描述出来了,所以结合起来看应该能够理解整体流程~

总结

本次分析了 beanFactory 的后处理器 BeanFactoryPostProcessor ,了解了 Spring 给我们提供的这个扩展接口使用用途和在源码中如何进行激活执行。

由于个人技术有限,如果有理解不到位或者错误的地方,请留下评论,我会根据朋友们的建议进行修正

因为我也是一边看书,一边做笔记,下载了源码。 代码和注释都在里面,小伙伴们可以下载我上传的代码,亲测可运行~

spring-analysis-note 码云 Gitee 地址

spring-analysis-note Github 地址

参考资料

  1. Bean 定义
  2. Sping 的 BeanFactory 容器
  3. Spring拓展接口之BeanFactoryPostProcessor,占位符与敏感信息解密原理
原文  http://www.justdojava.com/2019/06/25/spring-analysis-note-6/
正文到此结束
Loading...