Spring源码解读之BeanFactoryPostProcessor的处理

前言

本文记录一下最近学习所得,是介绍BeanFactoryPostProcessor跟BeanPostProcessor是如何发挥作用的。现在就用这篇文章讲一下我对BeanFactoryPostProcessor的理解,争取把重点讲到,至于BeanPostProcessor,由于涉及到的东西太多,限于本人目前的水平只能作罢,待后面感悟成熟了再来补充。

我们以

AnnotationConfigApplicationContext为例来构建测试类,先附上此次打断点调试的三个简约到极致的测试类:

public class SpringTest {

public static void main(String[] args) {

// 从这两行代码,实地跟踪考察Spring中的流程

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);

applicationContext.getBean(Teacher.class).teach();

}

}

package myPackage;

import org.springframework.stereotype.Service;

@Service

public class Teacher {

public Teacher () {

System.out.println("Teacher init");

}

public void teach () {

System.out.println("teach");

}

}

1 package myPackage;

2 import org.springframework.context.annotation.ComponentScan;

3

4 @ComponentScan("myPackage")

5 public class ScanConfig {

6 }

1、洞悉启动容器时的准备工作

熟悉一些Spring的道友应该都知道,refresh方法中的

invokeBeanFactoryPostProcessors方法实现了对BeanFactoryPostProcessor实现类的处理。大家如果只看invokeBeanFactoryPostProcessors方法的话,不会发现有何异常之处,此方法虽然较长,但是处理逻辑很清晰,先对重写了BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor方法的实现类进行处理,后对重写了BeanFactoryPostProcessor的方法的实现类做了处理。但是如果心细的话,你会发现问题,Spring是如何将@ComponentScan(“myPackage”)注解发挥作用的?这时带着这样的问题,我们再回过头来看容器的构造方法,就会在这平实的表面下发现意想不到的 “杀机”。

1 public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {

2 this();

3 register(annotatedClasses);

4 refresh();

5 }

通过这个构造方法可以知道,在第二行将我们的测试类ScanConfig 注册进了容器中,但这只是注册,注册之后是如何调用如何实现了@ComponentScan(“myPackage”)这个注解的包扫描的呢?这时我们将目光锁定this()方法。点进去后发现是这样的

1 public AnnotationConfigApplicationContext() {

2 this.reader = new AnnotatedBeanDefinitionReader(this);

3 this.scanner = new ClassPathBeanDefinitionScanner(this);

4 }

在第二行新建reader对象时,调用了这个构造方法:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

Assert.notNull(environment, "Environment must not be null");

this.registry = registry;

this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);

}

其中的第六行,最终调用了AnnotationConfigUtils#

registerAnnotationConfigProcessors方法,而就是在这个方法中完成了对多个重要Bean的注册,跟我们关系比较大的有以下几个:

// BeanDefinitionHolder只是存放BD的,里面有三个属性:BD对象、beanName以及别名组成的String[]

Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);

// 注册最关键的类,对应的类为ConfigurationClassPostProcessor,父类的父类是BeanFactoryPostProcessor

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {

RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);

def.setSource(source);

// 将BD注入进容器中,没经过什么处理,只是放入了DefaultListableBeanFactory中的beanDefinitionMap跟存放beanName的list中

beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));

}

// 此类实现了BeanPostProcessor,用于处理@Autowired、@Value注解

if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {

RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);

def.setSource(source);

beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));

}

// 此类也实现了BeanPostProcessor,用于处理有@Required注解的方法

if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {

RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);

def.setSource(source);

beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));

}

其中第一个对应的类就是我们重点关注的对象 

ConfigurationClassPostProcessor类,查看此类的组成,发现它实现了BeanDefinitionRegistryPostProcessor接口,而此接口正是BeanFactoryPostProcessor的子接口。此时,萦绕在我们心头的迷雾开始渐渐散开,我们仿佛能抓到一闪而过的逻辑走向,现在让我们带着之前的发现,进入正主invokeBeanFactoryPostProcessors方法中一探究竟。

2、invokeBeanFactoryPostProcessors

该方法位于

AbstractApplicationContext的refresh方法中,如下所示:

public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

// Prepare this context for refreshing.

prepareRefresh();

// Tell the subclass to refresh the internal bean factory.

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.

prepareBeanFactory(beanFactory);

try {

// Allows post-processing of the bean factory in context subclasses.

postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.

invokeBeanFactoryPostProcessors(beanFactory);

即第17行调用的方法。初学者看到这个方法的内部实现时,会发现此方法无外乎是找到所有实现了

BeanDefinitionRegistryPostProcessor跟BeanFactoryPostProcessor接口的类,按照优先级(实现了PriorityOrdered接口的先于实现了Ordered接口,前两者均先于未实现的,且同一类中按照getOrder方法返回值排优先级)顺序执行它们的重写方法,先执行BeanDefinitionRegistryPostProcessor的重写方法,再执行BeanFactoryPostProcessor的重写方法,很容易理解的逻辑。但是现在我们是带着前面准备工作中得到的线索来的,此时再看,就能透过这个方法朴实的外表发现它潜藏的凶险,它如平地一声雷般让人猛地惊出一身冷汗。

我们打断点进入

PostProcessorRegistrationDelegate类中的下面方法中

1 public static void invokeBeanFactoryPostProcessors(

2 ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {}

先略过开始对手动添加进去的beanFactoryPostProcessors处理逻辑,看后面的部分代码(由于此方法代码较多,此处就不全部粘贴出来了,因为逻辑很好理解,所以只粘贴重点):

String[] postProcessorNames =

beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);

// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.

List<BeanDefinitionRegistryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanDefinitionRegistryPostProcessor>();

// 1.2 先处理实现了PriorityOrdered的类

for (String ppName : postProcessorNames) {

if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {

// 此处通过getBean来生成ConfigurationClassPostProcessor实例对象

priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

processedBeans.add(ppName);

}

}

// 就一个对象,有啥好排序的

sortPostProcessors(beanFactory, priorityOrderedPostProcessors);

registryPostProcessors.addAll(priorityOrderedPostProcessors);

// 执行ConfigurationClassPostProcessor中的重写方法postProcessBeanDefinitionRegistry,会将所有加了注解的类注册到容器中

// 此处才是整个invokeBeanFactoryPostProcessors方法的核心所在,需要详述 下面进入ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry中一探究竟

invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry);

debug到第一行的时候,会发现此处获取到的postProcessorNames 中只有一个值,就是前面准备工作中通过硬编码往容器里注册的

ConfigurationClassPostProcessor类。下面的逻辑就是进行各种判断,最后在第19行完成了对ConfigurationClassPostProcessor中postProcessBeanDefinitionRegistry方法的调用。

就是在这个后置处理方法中,完成了@ComponentScan(“myPackage”)中对包的扫描,完成了所有Bean的注册。执行完这个方法后,你会发现beanDefinitionMap中所有应该容器管理的类全都齐活了,包括其他的后置处理器。这样,后面继续调用

beanFactory.getBeanNamesForType方法时,获取到的是所有满足条件的类,后面的工作就会有条不紊的开展下去了。

总结

本文着重追溯了BeanFactoryPostProcessor及其子接口是如何在Spring中发挥作用的。先通过Spring初始化容器时注册进去的

ConfigurationClassPostProcessor类触发对其他类的扫描,待全部注册进容器后,再从容器中取对应的BeanFactoryPostProcessor及其子接口的实现类,逐一对重写方法进行调用。

虽然魔鬼在细节,但这也正是解读源码的快乐之处,不是吗?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容