Spring BeanFactoryPostProcessor 的作用时机详解

2023-09-20 09:03:51 浏览数 (1)

在前面的文章中,松哥已经和大家分享了 BeanPostProcessor 和 BeanFactoryPostProcessor 的基本用法以及差别,但是没有去分析其原理,今天我们就来聊一聊这两个后置处理器的工作原理。

基本用法我这里就不罗嗦了,不熟悉的小伙伴可以参考之前的文章:

  • BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别?

原理我打算从两个方面来讲,第一方面就是在容器初始化的时候,将系统中存在的 BeanFactoryPostProcessor 和 BeanPostProcessor 收集起来,第二步则是在适当的时机去应用对应的处理器,我们分别来。

1. 收集后置处理器

收集后置处理器就是在项目启动的时候,将项目中的后置处理器都收集起来,将来在某一个时间点去使用。

小伙伴们知道,我们平时开发基本上都是使用的 ApplicationContext 容器,那么容器的初始化基本上就是从 refresh 方法开始的。

在 refresh 方法中,会调用到 invokeBeanFactoryPostProcessors 方法,这个方法看名字就是调用所有的 BeanFactoryPostProcessor 的,我们来看下这个方法的具体内容:

代码语言:javascript复制
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
 //省略其他
}
public static void invokeBeanFactoryPostProcessors(
  ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
 Set<String> processedBeans = new HashSet<>();
 if (beanFactory instanceof BeanDefinitionRegistry registry) {
  List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
  List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
  for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
   if (postProcessor instanceof BeanDefinitionRegistryPostProcessor registryProcessor) {
    registryProcessor.postProcessBeanDefinitionRegistry(registry);
    registryProcessors.add(registryProcessor);
   }
   else {
    regularPostProcessors.add(postProcessor);
   }
  }
  List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
  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, beanFactory.getApplicationStartup());
  currentRegistryProcessors.clear();
  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);
  invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
  currentRegistryProcessors.clear();
  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, beanFactory.getApplicationStartup());
   currentRegistryProcessors.clear();
  }
  invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
  invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
 }
 else {
  invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
 }
 String[] postProcessorNames =
   beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

 List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
 List<String> orderedPostProcessorNames = new ArrayList<>();
 List<String> nonOrderedPostProcessorNames = new ArrayList<>();
 for (String ppName : postProcessorNames) {
  if (processedBeans.contains(ppName)) {
  }
  else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
   priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
  }
  else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
   orderedPostProcessorNames.add(ppName);
  }
  else {
   nonOrderedPostProcessorNames.add(ppName);
  }
 }
 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);
 beanFactory.clearMetadataCache();
}

先看第一个 invokeBeanFactoryPostProcessors 方法,这个虽然在调用代理的 invokeBeanFactoryPostProcessors 方法时传入了一个 BeanFactoryPostProcessor 集合,但是一般情况下,这个集合是空的,所以真正需要处理的 BeanFactoryPostProcessor 都是在下面的方法中现场查询出来的。

那么核心的逻辑就处于第二个方法之中了。第二个方法特别长,但是逻辑其实并不难懂。

不过在看第二个方法之前,需要大家先了解一个前置知识,即 BeanFactoryPostProcessor 有一个名为 BeanDefinitionRegistryPostProcessor 的子类,BeanDefinitionRegistryPostProcessor 是 Spring 框架中的一个接口,它的作用是在应用程序上下文启动时,对 BeanDefinitionRegistry 进行后置处理。具体来说,BeanDefinitionRegistryPostProcessor 可以用于修改或扩展应用程序上下文中的 BeanDefinition,即在 Bean 实例化之前对 BeanDefinition 进行修改。它可以添加、删除或修改 BeanDefinition 的属性,甚至可以动态地注册新的 BeanDefinition。通过实现 BeanDefinitionRegistryPostProcessor 接口,我们可以在 Spring 容器启动过程中干预 Bean 的定义,以满足特定的需求。这使得我们可以在应用程序上下文加载之前对 Bean 进行一些自定义的操作,例如动态注册 Bean 或者修改 Bean 的属性。需要注意的是,BeanDefinitionRegistryPostProcessor 在 BeanFactoryPostProcessor 之前被调用,因此它可以影响到 BeanFactoryPostProcessor 的行为。

有了这个前置知识,那么再去理解上面的代码就容易多了,上面在处理的过程中,会先判断是不是 BeanDefinitionRegistryPostProcessor 类型的后置处理器,如果是,就先解决这个问题,把所有的 BeanDefinitionRegistryPostProcessor 类型的后置处理器都处理完毕,才会处理 BeanFactoryPostProcessor。

悄悄告诉大家,其实 BeanDefinitionRegistryPostProcessor 只有一个实现类,这个实现类用来处理 @Configuration 注解,相关内容松哥之前已经发了一道经典面试题:@Configuration 和 @Component 有何区别?

再回到 invokeBeanFactoryPostProcessors 方法,我们看到,方法首先定义了 processedBeans 变量,这个变量用来记录所有已经处理过的后置处理器。

接下来会去判断容器的类型是否为 BeanDefinitionRegistry,只有是 BeanDefinitionRegistry 类型的容器,BeanDefinitionRegistryPostProcessor 才能被处理。如果容器是 BeanDefinitionRegistry 类型,那么就声明两个变量,分别是 regularPostProcessors 和 registryProcessors,其中,前者用来保存 BeanFactoryPostProcessor 类型的后置处理器,后者用来保存 BeanDefinitionRegistryPostProcessor 类型的处理器。

接下来就对传入的集合进行遍历,判断是否为 BeanDefinitionRegistryPostProcessor 类型的后置处理器,如果是,则直接调用 registryProcessor.postProcessBeanDefinitionRegistry(registry); 方法去执行后置处理器,然后将后置处理器存入到 registryProcessors 集合中(以便于将来执行其 postProcessBeanFactory 方法);如果不是 BeanDefinitionRegistryPostProcessor 类型的后置处理器,那么就先将后置处理器存入到 regularPostProcessors 集合中(以便于将来执行其 postProcessBeanFactory 方法),后续统一调用。由于 BeanDefinitionRegistryPostProcessor 继承自 BeanFactoryPostProcessor,所以对于 BeanDefinitionRegistryPostProcessor 类型的处理器,不仅有来自 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法,还有它自身的 postProcessBeanDefinitionRegistry 方法。后续代码的整体处理思路是遇到 postProcessBeanDefinitionRegistry 方法就先执行,然后把 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 都有的 postProcessBeanFactory 方法放到最后统一执行,前面说的 regularPostProcessors 和 registryProcessors 两个变量就是干这个事情的。

接下来从 Spring 容器中查找所有的 BeanDefinitionRegistryPostProcessor,找到之后,如果这些 BeanDefinitionRegistryPostProcessor 实现了 PriorityOrdered 接口,则将之收集到 currentRegistryProcessors 集合中,同时也记录到 processedBeans 集合中,接下来对 currentRegistryProcessors 集合按照 PriorityOrdered 接口及逆行排序,并将排序后的结果存入到 registryProcessors 中(以便于将来执行其 postProcessBeanFactory 方法),然后执行 invokeBeanDefinitionRegistryPostProcessors 方法,该方法最终就会触发 postProcessBeanDefinitionRegistry。

接下来的一段代码和上面的逻辑类似,只不过这次是处理实现了 Ordered 接口的 BeanDefinitionRegistryPostProcessor。

带排序功能的 BeanDefinitionRegistryPostProcessor 处理完成后,接下来就是处理哪些不带有排序功能的 BeanDefinitionRegistryPostProcessor,也就是 while 循环中的内容了。

当所有的 BeanDefinitionRegistryPostProcessor 都处理完成之后,接下来调用 invokeBeanFactoryPostProcessors 方法去处理 regularPostProcessors 和 registryProcessors 集合中的对象,最终都是执行 postProcessBeanFactory 方法。

BeanDefinitionRegistryPostProcessor 的处理到此结束,接下来就是 BeanFactoryPostProcessor 的处理了。

首先去 Spring 容器中查找所有的 BeanFactoryPostProcessor,并将查询结果保存在 postProcessorNames 变量中,接下来声明了三个变量:

  • priorityOrderedPostProcessors:这个集合用来保存实现了 PriorityOrdered 接口的 BeanFactoryPostProcessor。
  • orderedPostProcessorNames:这个集合用来保存实现了 Ordered 接口的 BeanFactoryPostProcessor。
  • nonOrderedPostProcessorNames:这个集合用来保存不具备排序能力的 BeanFactoryPostProcessor。

一共就这三个集合,接下来遍历 postProcessorNames,给这三个集合填充数据,填充的时候,判断一下 bean 是否已经存在于 processedBeans 集合中,如果存在,就不用处理了。

最后,该排序的排序,排完序之后,调用 invokeBeanFactoryPostProcessors 方法继续处理即可。

2. 应用后置处理器

上面涉及到后置处理器应用的主要是两个方法,分别是:

  • invokeBeanDefinitionRegistryPostProcessors
  • invokeBeanFactoryPostProcessors

这两个方法的逻辑都比较简单,就一起看了:

代码语言:javascript复制
private static void invokeBeanDefinitionRegistryPostProcessors(
  Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {

 for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
  StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
    .tag("postProcessor", postProcessor::toString);
  postProcessor.postProcessBeanDefinitionRegistry(registry);
  postProcessBeanDefRegistry.end();
 }
}
private static void invokeBeanFactoryPostProcessors(
  Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

 for (BeanFactoryPostProcessor postProcessor : postProcessors) {
  StartupStep postProcessBeanFactory = beanFactory.getApplicationStartup().start("spring.context.bean-factory.post-process")
    .tag("postProcessor", postProcessor::toString);
  postProcessor.postProcessBeanFactory(beanFactory);
  postProcessBeanFactory.end();
 }
}

都是遍历,然后执行目标方法。

3. 具体应用

3.1 ConfigurationClassPostProcessor

这个类就是 BeanDefinitionRegistryPostProcessor 的一种,它的作用是用来处理 @Configuration 注解的 Full 模式和 Lite 模式。这个松哥在之前的文章中已经详细介绍过了,感兴趣的小伙伴可以参考这里:一道经典面试题:@Configuration 和 @Component 有何区别?

3.2 Properties 文件处理

Properties 文件处理也是通过 BeanFactoryPostProcessor 来实现的。

当我们在 Spring 容器中配置数据源的时候,一般都是按照下面这样的方式进行配置的。

首先创建 db.properties,将数据源各种信息写入进去:

代码语言:javascript复制
db.username=root
db.password=123
db.url=jdbc:mysql:///db01?serverTimezone=Asia/Shanghai

然后在 Spring 的配置文件中,首先把这个配置文件加载进来,然后就可以在 Spring Bean 中去使用对应的值了,如下:

代码语言:javascript复制
<context:property-placeholder location="classpath:db.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
    <property name="url" value="${db.url}"/>
</bean>

但是大家知道,对于 DruidDataSource 来说,毫无疑问,它要的是具体的 username、password 以及 url,而上面的配置很明显中间还有一个转换的过程,即把 {db.username}、{db.password} 以及

这就得分析 <context:property-placeholder location="classpath:db.properties"/> 配置了,小伙伴们知道,这个配置实际上是一个简化的配置,点击去可以看到真正配置的 Bean 是 PropertySourcesPlaceholderConfigurer,而 PropertySourcesPlaceholderConfigurer 恰好就是 BeanFactoryPostProcessor 的子类,我们来看下这里是如何重写 postProcessBeanFactory 方法的:

源码比较长,松哥这里把一些关键部分列出来和小伙伴们展示:

代码语言:javascript复制
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
 if (this.propertySources == null) {
     //这里主要是如果没有加载到 properties 文件,就会尝试从环境中加载
 }
    //这个就是具体的属性转换的方法了
 processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
 this.appliedPropertySources = this.propertySources;
}
/**
 * 这个属性转换方法中,对配置文件又做了一些预处理,最后调用 doProcessProperties 方法处理属性
 */
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
  final ConfigurablePropertyResolver propertyResolver) throws BeansException {
 propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
 propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
 propertyResolver.setValueSeparator(this.valueSeparator);
 StringValueResolver valueResolver = strVal -> {
  String resolved = (this.ignoreUnresolvablePlaceholders ?
    propertyResolver.resolvePlaceholders(strVal) :
    propertyResolver.resolveRequiredPlaceholders(strVal));
  if (this.trimValues) {
   resolved = resolved.trim();
  }
  return (resolved.equals(this.nullValue) ? null : resolved);
 };
 doProcessProperties(beanFactoryToProcess, valueResolver);
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
  StringValueResolver valueResolver) {
 BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
 String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
 for (String curName : beanNames) {
  // Check that we're not parsing our own bean definition,
  // to avoid failing on unresolvable placeholders in properties file locations.
  if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
   BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
   try {
    visitor.visitBeanDefinition(bd);
   }
   catch (Exception ex) {
    throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
   }
  }
 }
 // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
 beanFactoryToProcess.resolveAliases(valueResolver);
 // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
 beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

上面第三个 doProcessProperties 方法我要稍微和小伙伴们说两句:

使用 PropertySourcesPlaceholderConfigurer 对配置中的占位符进行处理,虽然我们只是在 DruidDataSource 中用到了相关变量,但是系统在处理的时候,除了当前这个配置类之外,其他的 Bean 都要处理(因为你可以在任意 Bean 中注入那三个变量)。

这就是 BeanFactoryPostProcessor 一个经典实践,即在 Bean 初始化之前,把 Bean 定义时候的一些占位符给改过来。

0 人点赞