⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7263479207124942907 「jack_xu」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

1、初始化器ApplicationContextInitializer

我们在启动Spring Boot项目的时候,是执行这样一个方法来启动的

我们一层一层往下点,最终发现执行的是这个方法

所以我们在启动项目的时候也可以这样启动 new SpringApplication(SpringbootExtensionPointApplication.class).run(args); 老的只是包装了一个静态方法,实际底层就是实例化一个SpringApplication对象,然后调用它的run方法。

我们进到构造方法里看下,红框里面标出来的,一个是设置初始化器,一个是设置监听器。

点进去看下,这两个底层调的方法是一样,就是传入一个类型,通过Spring SPI的方式查找这个类型的实现类

打个断点,启动一下,此时有7个上下文初始器,这是系统自带的,配置在不同的spring.factories文件中。

现在我要新建一个自己的初始化器

此时为了能够让Spring Boot在启动的时候能够扫描到我创建的初始化器应该怎么办?就是在spring.factories文件中添加一下,注册一下,这样就能扫描到,这个就是SPI。SPI 全称为 Service Provider Interface,是一种服务发现机制。

那么这时候我们再启动一下Spring Boot,发现自己创建的ApplicationContextInitializer也已经注册上来了,变成8个了。

把断点放掉,在控制台中也打印出了这句话,那么这个就是第一个扩展点ApplicationContextInitializer

定义了这8个初始化器,那一定是有地方在调它们的,不然怎么会打印出来呢,那具体在什么地方调的,我们在自己的初始化器的地方打断点,看到已经进来了,然后看下方的堆栈信息,这个就是调用的地方。

原来是调用了run()方法中的prepareContext()方法中的applyInitializers()方法,在这个方法中for循环的调用各个初始化器的initialize()方法,从而我们就能看到把Jack的ApplicationContextInitializer这句话给打印出来了。

那么这个查找的方法就是以结果为导向来反查调用方,因为你正查的话是很难找到,很难记住的,这个方法希望大家学习到。

那么最后来看下我们第一个扩展点所处的位置

初始化器可以做一些事情,比如Environment对象设置一些变量配置。

2、监听器ApplicationListener

在上面构造函数里我们发现除了有setInitializers,还有setListeners,那么这个listeners其实也是一个扩展点。

那么什么是监听器,就是这样的,这个其实就是观察者模式,ApplicationEventMulticaster发布事件,各个Listener监听事件。

和初始化器一样,现在我们自定义两个监听器,一个是Starting,一个是Started,括号里面的是泛型,这个是一定要写的,如果不写的话就是不管什么类型的Event都会监听。

这个泛型是上限为ApplicationEvent类型的Event,具体的实现类有很多个,Starting和Started只是其中两个。

现在我们还是把这两个监听器通过SPI的方式加到配置中去

好,运行一下,我们看到这两句话已经打印出来了

和监听器一样,既然能够打印出来,那肯定是有地方在调用,所以我们在JackStartingApplicationListener打个断点,然后看下堆栈信息

我们可以看到在SpringApplication run()方法的listeners.starting()开始进去发送ApplicationStartingEvent广播事件,最后发布出去,由我们自己编写的事件监听器接收到。

那么ApplicationStartedEvent事件也是一样的道理,通过打断点的方式来找到它的调用方,最后我们再来看下此时的扩展点图

3、Runner

我们看到在listeners.started()后面有个callRunners

我们点进去看下,它其实就是从容器中获取两种类型的Runner,一种是ApplicationRunner,还有一种是CommandLineRunner,然后for循环的对它们进行调用,那么其实这个也是一个扩展点

我们来写一个自己的Runner

运行一下,看下打印出来了

那么这个Runner的一般应用场景就是资源释放清理或者做注册中心,因为执行到Runner的时候项目已经启动完毕了,这时候就可以注册到注册中心上去了。此时我们再看下扩展点图。

4、BeanFactoryPostProcessor

我们看下run方法里的refreshContext()方法

这个方法底层会调spring里面的refresh()方法,这个方法里面就会做对容器的初始化。红框里的invokeBeanFactoryPostProcessors()方法,这里也有一个扩展点,就是BeanFactoryPostProcessor,执行对BeanFactory的后置处理。Spring Boot解析配置成BeanDefinition的操作也是在此方法中。

现在我们来创建一个自己的BeanFactoryPostProcessor,这个方法里面可以修改beanFactory的属性,beanfactory里面有BeanDefinition,可以修改BeanDefinition里面的值。BeanDefinition是一个bean的元数据的信息,有多少个bean就有多少个BeanDefinition。

运行一下,也打印出来了

此时我们再看下扩展点图,越来越完善了。

5、BeanPostProcessor

最后介绍的是BeanPostProcessor,它在通过反射构造函数进行bean实例化之后执行,那么红框里面标出来的registerBeanPostProcessors()方法就是向BeanFactory中注册beanpostprocessor,用于后续bean创建的拦截操作。

现在我们来创建一个自己的BeanPostProcessor,一共有两个方法,postProcessBeforeInitialization和postProcessAfterInitialization,不过我们一般用postProcessAfterInitialization,在bean调用反射构造函数实例化之后执行。著名的应用场景AOP底层就是通过BeanPostProcessor来实现的。

现在我在postProcessAfterInitialization上打个断点,看下堆栈信息是在哪里调用的

是在finishBeanFactoryInitialization()方法处调用的

后记

最后我来把扩展点图补充完整,如下所示,很清晰明了,在什么时候调用了什么,我们自己开发的时候结合应用场景,在什么时候要干什么事,就知道要创建什么类型的扩展点了。

本文前三个讲的是Spring Boot里面自己有的扩展点,后两个因为Spring Boot底层调的是Spring的源码,所以属于Spring里面的扩展点,所以如果这么算的话Spring里面的扩展点还有很多扩展点,比如InitializeBean、Aware等等这里都没讲,等待大家去发掘,谢谢观看 ~

文章目录
  1. 1. 1、初始化器ApplicationContextInitializer
  2. 2. 2、监听器ApplicationListener
  3. 3. 3、Runner
  4. 4. 4、BeanFactoryPostProcessor
  5. 5. 5、BeanPostProcessor
  6. 6. 后记