简单易懂的Spring扩展点原理,看不懂你来打我

总览

这里罗列的Spring扩展点,其实不是Spring的所有扩展点,仅仅是Spring的一部分扩展点,但是这些扩展点是我觉得相对重要的。

先上一幅总览图,以下就是本文要讲的Spring扩展点。
在这里插入图片描述

当然,在具体描述每个扩展点前,先要弄清楚什么叫Spring扩展点?

什么是Spring扩展点?>

Spring扩展点,我认为就是Spring预留的一系列接口,这些接口可以实现对Spring的扩展,可以让开发者完成一些Spring核心流程以外的定制化的操作

定制化操作,举个例子:Mybatis与Spring对接,利用Spring的扩展点ImportBeanDefinitionRegistrar和BeanDefinitionRegistryPostProcessor(这两个接口下面会介绍到),完成Mapper的扫描和创建,放入到Spring容器中。这就是定制化操作,在Spring的核心流程以外,通过Spring预留的接口,实现定制化功能。

这些接口,会在Spring容器初始化的不同阶段,得到回调
在这里插入图片描述

除此以外,还要弄懂的是,学习Spring的扩展点,究竟要学习哪些内容?

学习Spring的扩展点,究竟要学习哪些内容?

  • 首先要学习的是扩展点的作用,就是它可以用来干嘛?这个可以结合现成的例子来看,例如其他开源框架与Spring的整合。

  • 然后要弄清楚的是,它会在Spring容器初始化的哪个阶段,哪个节点,得到回调,也就是回调时机

  • 最后,我们要学习的就是这个扩展点的原理
    在这里插入图片描述

本文讲述Spring扩展点的方式,也是按照这种思路来进行。

另外,学习Spring的扩展点,还要对Spring的核心流程有所了解,也就是Spring容器的初始化,都有哪些步骤。

Spring的核心流程

下图就是Spring的核心流程,因为罗列的是核心流程,所以一些非核心的分支就抽离出去了。
另外因为本文要讲解的是Spring的扩展点,所以扩展点也抽离出去了,等下面讲到某个扩展点的时候,再把它插入进去。
在这里插入图片描述

如果对Spring的核心流程不太了解的同学,可以看一下另一篇文章:
人人都能看懂的Spring底层原理,看完绝对不会懵逼

BeanFactoryPostprocessor

BeanFactoryPostprocessor的作用

BeanFactoryPostprocessor叫做Bean工厂后置处理器,它的作用就是接收一个BeanFactory参数,然后我们可以自定义修改BeanFactory里面的BeanDefinition

@FunctionalInterface
public interface BeanFactoryPostProcessor {
<span class="token keyword">void</span> <span class="token function">postProcessBeanFactory</span><span class="token punctuation">(</span><span class="token class-name">ConfigurableListableBeanFactory</span> beanFactory<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeansException</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以上就是BeanFactoryPostProcessor 的接口定义,接收的参数是一个BeanFactory,此时的BeanFactory已经完成了对BeanDefinition的扫描和加载,我们可以实现该接口,自定义修改容器里面的的bean定义。
在这里插入图片描述

修改了BeanDefinition,那么后续Spring根据BeanDefinition去实例化和初始化bean的时候,就会跟原来的不一样。好比建房子要根据设计图纸,现在我们对设计图纸做修改,那么后面建出来的房子,就跟原来设计的不一样。

在这里插入图片描述

BeanFactoryPostprocessor的回调时机

BeanFactoryPostProcessor 的回调时机,是在Spring完成对配置信息的扫描,加载BeanDefinition到容器中之后,在实例化bean之前。因为这样才能通过修改BeanDefinition,进而控制后续bean的实例化和初始化。
在这里插入图片描述

BeanFactoryPostprocessor的原理

BeanFactoryPostProcessor 的实现原理比较简单。只要弄清楚BeanFactoryPostProcessor 是怎么放入容器的,然后就是怎么被Spring回调的

BeanFactoryPostProcessor 的来源有两处:
1、直接通过 AbstractApplicationContext#addBeanFactoryPostProcessor(BeanFactoryPostProcessor) 方法,把我们自己定义的BeanFactoryPostProcessor 添加到上下文中
2、通过XML或者注解的方式,把BeanFactoryPostProcessor 定义为一个bean,等待Spring扫描

通过以上两种方式,Spring都可以获取到我们定义的BeanFactoryPostProcessor 。

然后就是如何回调我们的BeanFactoryPostProcessor :

  • 如果是我们通过AbstractApplicationContext#addBeanFactoryPostProcessor方法直接添加的BeanFactoryPostProcessor,Spring可以直接回调
  • 如果是把BeanFactoryPostProcessor定义为一个bean的方式,则Spring要以beanFactory.getBean(…) 先加载BeanFactoryPostProcessor。调beanFactory.getBean(…)方法前,需要先获取到beanName,所以要先通过beanFactory.getBeanNamesForType(…)方法获取对应类型的所有beanName

在这里插入图片描述
但是基于BeanFactoryPostProcessor做扩展的例子,其实并不多,更多的是基于它的子类BeanDefinitionRegistryPostProcessor做扩展。所以下面要介绍的就是BeanDefinitionRegistryPostProcessor。

BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor的作用

BeanDefinitionRegistryPostProcessor其实也属于BeanFactoryPostProcessor,它直接继承了BeanFactoryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
<span class="token keyword">void</span> <span class="token function">postProcessBeanDefinitionRegistry</span><span class="token punctuation">(</span><span class="token class-name">BeanDefinitionRegistry</span> registry<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeansException</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5

可以看到它的postProcessBeanDefinitionRegistry方法接收一个BeanDefinitionRegistry 类型的参数

BeanDefinitionRegistry 也就是BeanDefinition注册表,其实就是我们最熟悉的DefaultListableBeanFactory本身,因为DefaultListableBeanFactory就直接实现了BeanDefinitionRegistry 接口,所以DefaultListableBeanFactory既是一个bean工厂,也是一个BeanDefinition注册表。

然后我们就可以往BeanDefinitionRegistry 里面注册一些我们自己定义的BeanDefinition,然后Spring就会帮我们加载我们自己定义的bean。
在这里插入图片描述

下面来看一个例子,通过这个例子看看这个BeanDefinitionRegistryPostProcessor 的作用。

一个例子:Spring对@Configuration、@ComponentScan、@Component、@Bean、@Import等注解的处理

Spring对 @Configuration、@ComponentScan、@Component、@Bean、@Import 等注解的处理,就是通过实现一个BeanDefinitionRegistryPostProcessor 类型,去完成这些注解的扫描和加载。

这个BeanDefinitionRegistryPostProcessor 实现类就是ConfigurationClassPostProcessor,顾名思义,就是配置类后置处理器,也就是专门处理配置类的,也就是被**@Configuration注解修饰的类**。

我们如果通过注解配置方式启动Spring的话,会使用到AnnotationConfigApplicationContext,我们会定义一个被@Configuration注解修饰的配置类,作为AnnotationConfigApplicationContext的构造方法参数,而ConfigurationClassPostProcessor就会以这个配置类为入口,进行递归的加载和解析

比如我们的配置类上又有@ComponentScan注解,然后 @ComponentScan又加载了其他被@Configuration修饰的配置类,那么它就会递归解析这些配置类,直到所有的配置类的加载解析完毕。

加载的这些配置类、普通类,会以BeanDefinition的形式注册到BeanDefinitionRegistry (BeanDefinition注册表)。
在这里插入图片描述

通过这一顿递归的加载和解析,最终所有的配置类、普通类,都会以BeanDefinition的形式,添加到容器中,Spring就会帮我们创建和初始化这些额外的bean。

BeanDefinitionRegistryPostProcessor 的回调时机

BeanDefinitionRegistryPostProcessor 因为是BeanFactoryPostProcessor的子类,所以回调时机很明显跟BeanFactoryPostProcessor是一样的,但是严格来说,应该是在BeanFactoryPostProcessor的之前回调。
在这里插入图片描述

BeanDefinitionRegistryPostProcessor 的原理

BeanDefinitionRegistryPostProcessor因为是BeanFactoryPostProcessor的子类,所以实现原理自然也是跟BeanFactoryPostProcessor一样的。

BeanDefinitionRegistryPostProcessor也跟BeanFactoryPostProcessor一样,可以手动添加到Spring的上下文中,也可以通过XML或者注解以bean的形式配置。

在这里插入图片描述

ImportSelector

ImportSelector的作用

ImportSelector也是可以用于往Spring容器里添加一些bean的,它通常用于导入一些外部bean

什么是外部bean内呢?我的理解就是非Spring自己的,也不是我们定义的,而是一些jar包里面定义好的,比如SpringBoot的自动装配机制导入的一些其他jar包里的bean,就是外部bean

public interface ImportSelector {
<span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">selectImports</span><span class="token punctuation">(</span><span class="token class-name">AnnotationMetadata</span> importingClassMetadata<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token annotation punctuation">@Nullable</span>
<span class="token keyword">default</span> <span class="token class-name">Predicate</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">&gt;</span></span> <span class="token function">getExclusionFilter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

ImportSelector 的selectImports返回一个String[]类型,这个String[]就是类的全限定名数组,也就是所有要导入的外部bean的类全限定名。Spring会拿到ImportSelector 的selectImports返回的类全限定名数组,加载为一个个的BeanDefinition,注册到Spring容器中。
在这里插入图片描述

下面也是看一个例子,来进一步理解ImportSelector 的作用。

一个例子:SpringBoot的自动装配机制

用过SpringBoot的同学应该都知道,在SpringBoot工程的启动类上面,要打上一个@SpringBootApplication注解。
而@SpringBootApplication里面,又嵌套了三个注解:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

@SpringBootConfiguration里面嵌套了@Configuration,所以表示我们的启动类是一个配置类。

@ComponentScan是Spring定义包扫描注解,但是这里没指定包扫描路径,那么就是以当前类所在包为根路径进行扫描。

@EnableAutoConfiguration里面嵌套了@Import(AutoConfigurationImportSelector.class),通过@Import导入一个AutoConfigurationImportSelector,这个AutoConfigurationImportSelector就是ImportSelector 的实现类。

在这里插入图片描述
AutoConfigurationImportSelector会通过ClassLoader读取所有jar包的resources目录下定义的META-INF/spring.factories文件。

spring.factories里是以key-value形式定义了可能会导入到容器中的类全限定名。key一般是接口或抽象类的全限定名,value是实现类的类全限定名,多个用逗号分隔

但spring.factories这里的key-value对应关系,也有可能不是接口或抽象类与实现类的对应关系,比如这里SpringBoot要加载的key是@EnableAutoConfiguration注解的类全限定名,value是各种配置类。

读取并解析spring.factories后,得到的是一个 Map<String, List< String >>类型,然后以EnableAutoConfiguration的类全限定名,取出对应的所有类全限定名,也就是以EnableAutoConfiguration的类全限定名为key,进行过滤

然就把这些过滤后的类全限定名,以数组形式返回

在这里插入图片描述

然后这些类全限定名,经过条件过滤(Spring里面的@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnExpression等注解),就会被Spring解析成BeanDefinition,注册到容器中,后续根据这些BeanDefinition进行bean的实例化和初始化,最后放入容器,我们就可以直接使用。

ImportSelector 的回调时机

ImportSelector 是通过@Import注解导入的,而@Import注解是通过上面说到的ConfigurationClassPostProcessor进行处理的,而ConfigurationClassPostProcessor又是BeanDefinitionRegistryPostProcessor,所以回调时机自然也是跟BeanDefinitionRegistryPostProcessor相同。
在这里插入图片描述

ImportSelector 的原理

ImportSelector 是通过@Import注解导入的,而@Import注解是通过上面说到的ConfigurationClassPostProcessor进行处理的,所以ImportSelector 的实现原理,自然就是通过ConfigurationClassPostProcessor触发它的回调。

ConfigurationClassPostProcessor又对@Import导入的不同类型做特殊处理,如果检查到导入的是ImportSelector 类型,会回调它的selectImports方法,获取到返回的类全限定名数组。

当然,因为ImportSelector 导入的可能又是一些配置类,比如导入的时@Configuration修饰的,或者又是一个ImportSelector ,所以会进行递归的解析。

在这里插入图片描述

@Import处理可以导入ImportSelector 类型和@Configuration修饰的配置类以外,还可以导入ImportBeanDefinitionRegistrar,ImportBeanDefinitionRegistrar同样也可以往容器中注册一些BeanDefinition。以下介绍的就是ImportBeanDefinitionRegistrar。

ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar的作用

ImportBeanDefinitionRegistrar也是Spring定义的扩展接口,允许我们实现自己的ImportBeanDefinitionRegistrar,然后通过我们自己实现的ImportBeanDefinitionRegistrar,可以往容器中注册一些我们自己的bean

public interface ImportBeanDefinitionRegistrar {
<span class="token keyword">default</span> <span class="token keyword">void</span> <span class="token function">registerBeanDefinitions</span><span class="token punctuation">(</span><span class="token class-name">AnnotationMetadata</span> importingClassMetadata<span class="token punctuation">,</span> <span class="token class-name">BeanDefinitionRegistry</span> registry<span class="token punctuation">,</span>
		<span class="token class-name">BeanNameGenerator</span> importBeanNameGenerator<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>

	<span class="token function">registerBeanDefinitions</span><span class="token punctuation">(</span>importingClassMetadata<span class="token punctuation">,</span> registry<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">default</span> <span class="token keyword">void</span> <span class="token function">registerBeanDefinitions</span><span class="token punctuation">(</span><span class="token class-name">AnnotationMetadata</span> importingClassMetadata<span class="token punctuation">,</span> <span class="token class-name">BeanDefinitionRegistry</span> registry<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

ImportBeanDefinitionRegistrar跟BeanDefinitionRegistryPostProcessor有点类似,它的registerBeanDefinitions方法也会接收一个BeanDefinitionRegistry 类型的参数,我们可以往这个BeanDefinition注册表里面注册一些BeanDefinition,这个BeanDefinitionRegistry 自然还是DefaultListableBeanFactory。

在这里插入图片描述

这里可能有人会问,为什么要用这么麻烦的方式取注册bean呢?正常通过@Configuration + @Bean或者XML的方式不就行了?包括上面的BeanDefinitionRegistryPostProcessor也是。

为什么要这样去注册bean?

如果我们做的时业务开发,写的是业务代码,可能确实用不着这样去注册bean,自然也就用不着这些接口。

但是如果我们是写底层框架呢?或者我们写了一个中间件要和Spring做整合呢?
我们的代码是以jar包的形式供别的开发者引入依赖的,这时我们就不能再用常规的方式去注册bean了,因为不可能让开发者通过正常的配置方式自己去整合我们的框架到Spring中,这样做就会非常麻烦。

比如假如Mybatis没有提供mybatis-spring这个整合Spring的包,那我们只能自己通过@Configuration + @Bean + SqlSession.getMapper(…),把我们的Mapper一个个的生成好放入Spring容器中,如果我们的Mapper非常多,那就非常麻烦。或者开发者只能自己写一个扫描的类,去扫描自己的Mapper,注册到容器中。
在这里插入图片描述

显然,我们应该提供一个整合Spring的包,让开发者去引入,然后通过简单的配置方式,就能把我们的框架整合到Spring中,然后就能直接使用。

而我们提供的整合自己框架到Spring的包,就可以通过这些Spring的扩展点,完成与Spring的整合。

这里还是看两个例子,去理解ImportBeanDefinitionRegistrar的作用。

一个例子:Mybatis与Spring整合,Mapper的扫描和加载

Mybatis整合Spring,使用到了ImportBeanDefinitionRegistrar和BeanDefinitionRegistryPostProcessor这两个扩展接口。

  1. 如果我们的工程是一个SpringBoot工程,一般会在启动类上打一个@MapperScan注解,这个是mybatis-spring包的一个注解,里面嵌套了一个@Import注解,@Import注解导入一个MapperScannerRegistrarMapperScannerRegistrar它是ImportBeanDefinitionRegistrar的实现类
  2. MapperScannerRegistrar会往Spring容器注册一个MapperScannerConfigurer,它是BeanDefinitionRegistryPostProcessor的实现类
  3. MapperScannerConfigurer会通过ClassPathMapperScanner(Mybatis自己实现的扫描器,继承了Spring的ClassPathBeanDefinitionScanner),扫描指定路径的Mapper接口。这里的ClassPathMapperScanner是Mybatis自己的扫描器,继承了Spring的扫描器ClassPathBeanDefinitionScanner,上面说到的ConfigurationClassPostProcessor处理@ComponentScan注解,就是通过ClassPathBeanDefinitionScanner对指定的包路径进行扫描的,扫描结果会返回一个BeanDefinition的Set集合
  4. 这里Mybatis通过自己的ClassPathMapperScanner扫描到指定包路径下的所有Mapper接口,然后会修改BeanDefinition里的bean的Class类型属性修改为MapperFactoryBean类型,最后把BeanDefinition注册到Spring容器中。
  5. MapperFactoryBean是FactoryBean接口的实现类,getObject方法会返回SqlSession#getMapper返回的代理对象

在这里插入图片描述

FactoryBean<T>

上面说到MapperFactoryBean实现了Spring提供的FactoryBean接口,FactoryBean接口其实是Spring提供给我们自己去进行bean的实例化和初始化的接口,也相当于是Spring的一个扩展点,只是本文没有单独罗列。

当我们有一些初始化工作非常复杂的bean,例如要进行各种配置,不方便交给Spring去管理它的初始化,此时我们可以通过让我们的bean实现FactoryBean接口,在FactoryBean接口的getObject方法里面去进行实例化和初始化。

public interface FactoryBean<T> {
<span class="token class-name">String</span> <span class="token constant">OBJECT_TYPE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"factoryBeanObjectType"</span><span class="token punctuation">;</span>

<span class="token class-name">T</span> <span class="token function">getObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span><span class="token punctuation">;</span>

<span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token operator">?</span><span class="token punctuation">&gt;</span></span> <span class="token function">getObjectType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">default</span> <span class="token keyword">boolean</span> <span class="token function">isSingleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

比如Mybatis的Mapper的初始化,要通过SqlSession#getMapper方法,通过动态代理生成代理类,而Spring是不会去调SqlSession#getMapper方法的,所以Mybatis只能自己实现FactoryBean接口,在getObject方法里面通过SqlSession#getMapper生成Mapper的代理类。
在这里插入图片描述
再比如Mybatis的SqlSessionFactoryBean,因为SqlSessionFactory的初始化比较复杂,要进行各种配置,解析各种Mapper.xml文件,这些初始化工作只能由Mybatis自己完成,所有只能实现FactoryBean接口,在getObject里面完成这些初始化工作,然后getObject方法返回的SqlSessionFactory将直接放入Spring容器。

在这里插入图片描述
实现了FactoryBean接口的bean放入容器后,如果我们再次调用getBean去获取,或者被其他bean依赖到,通过getBean获取,那么就会回调它的getObject方法,返回的就是getObject方法里面返回的bean。

在这里插入图片描述

一个例子:OpenFeign扫描@FeignClient注解生成Feign客户端

OpenFegin扫描 @FeignClient 注解生成Feign客户端的原理,可以说是和Mybatis整合Spring时扫描Mapper接口生成代理对象的做法如出一辙。

  1. 首先我们会在SpringBoot工程的启动类上打上一个 @EnableFeignClients注解,这个@EnableFeignClients嵌套了一个 @Import 注解,@Import注解导入了一个FeignClientsRegistrar类,FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口
  2. FeignClientsRegistrar里面也是通过scanner扫描指定包路径,扫描出指定包路径下的被@FeignClient注解修饰的类
  3. 然后扫描返回的BeanDefinition集合,修改bean的beanClass属性为FeignClientFactoryBean类型
  4. FeignClientFactoryBean也是实现了FactoryBean,getObject方法通过动态代理生成Feign客户端对象

在这里插入图片描述

ImportBeanDefinitionRegistrar的回调时机

因为ImportBeanDefinitionRegistrar也是要通过@Import注解导入的,所以回调时机自然也是和ImportSelector一样。
在这里插入图片描述

ImportBeanDefinitionRegistrar原理

因为ImportBeanDefinitionRegistrar跟ImportSelector一样,都是通过@Import导入,所以原理也是跟ImportSelector一样

在ConfigurationClassPostProcessor处理@Import注解时,发现是ImportBeanDefinitionRegistrar类型的,会回调它的registerBeanDefinitions方法

跟ImportSelector不同的是,ImportSelector返回的是类全限定名数组,由Spring解析并注册BeanDefinition。而registerBeanDefinitions则是接收一个BeanDefinitionRegistry(BeanDefinition注册表)参数,自己注册BeanDefinition到Spring容器中。

在这里插入图片描述

BeanPostProcessor

BeanPostProcessor的作用

以上的接口都是在bean实例化前,对BeanDefinition做操作的,而BeanPostProcessor,则是在bean实例化后,对bean做操作的。

BeanPostProcessor叫做bean后置处理器,是在bean实例化并完成依赖注入后,在初始化方法回调的前后,对bean做一些特殊处理的,甚至可以替换掉原来的bean,例如生成一个代理对象返回。

public interface BeanPostProcessor {
<span class="token keyword">default</span> <span class="token class-name">Object</span> <span class="token function">postProcessBeforeInitialization</span><span class="token punctuation">(</span><span class="token class-name">Object</span> bean<span class="token punctuation">,</span> <span class="token class-name">String</span> beanName<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeansException</span> <span class="token punctuation">{<!-- --></span>
	<span class="token keyword">return</span> bean<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">default</span> <span class="token class-name">Object</span> <span class="token function">postProcessAfterInitialization</span><span class="token punctuation">(</span><span class="token class-name">Object</span> bean<span class="token punctuation">,</span> <span class="token class-name">String</span> beanName<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeansException</span> <span class="token punctuation">{<!-- --></span>
	<span class="token keyword">return</span> bean<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

BeanPostProcessor接口有两个方法,一个是postProcessBeforeInitialization 初始化前处理,在初始化方法回调前被回调,另一个是postProcessAfterInitialization 初始化后处理,在初始化方法回调后再被回调。

一个例子一:Spring AOP的入口

BeanPostProcessor的第一个应用例子,就是Spring AOP,它是通过BeanPostProcessor的postProcessAfterInitialization 方法,检查bean是否需要进行AOP处理的。

Spring AOP 通过实现BeanPostProcessor的postProcessAfterInitialization 方法,在里面wrapIfNecessary方法,wrapIfNecessary方法检查当前bean是否有方法被AOP切面命中,如果有,则通过动态代理返回一个代理对象。
这里AOP的原理就不详细描述了。
在这里插入图片描述

一个例子:@PostConstruct注解修饰方法的回调

Spring给我们提供了一个 @PostConstruct 注解,我们可以在一个类的方法上面打上这个注解,声明这个方法会初始化方法,在bean的初始化阶段,Spring会回调这个初始化方法

而@PostConstruct注解修饰方法的回调,是通过BeanPostProcessor的postProcessBeforeInitialization 实现的。

在Spring里面,有一个InitDestroyAnnotationBeanPostProcessor,它间接实现了BeanPostProcessor接口,postProcessBeforeInitialization 方法会检查当前bean是否有被@PostConstruct注解修饰的方法,如果有会通过反射回调该方法。
在这里插入图片描述

BeanPostProcessor的回调时机

BeanPostProcessor是在bean实例化并完成依赖注入以后,在初始化方法回调的前后被回调的,before方法会在初始化方法回调前被回调,而after方法会在初始化方法回调后被回调。
在这里插入图片描述

BeanPostProcessor的原理

因为BeanPostProcessor是用于对bean做特殊处理的,所以Spring要在其他bean的实例前,先把所有BeanPostProcessor类型的bean初始化好

  1. 在Spring对所有非懒加载的单例bean进行预初始化前,会调用Spring上下文里面的registerBeanPostProcessors(beanFactory)方法进行BeanPostProcessor的注册
  2. BeanPostProcessor类型的bean的初始化,也是跟BeanFactoryPostProcessor一样,先通过beanFactory.getBeanNamesForType(…)获取指定类型的所有beanName,这里返回类型为BeanPostProcessor的beanName数组
  3. 然后通过beanFactory.getBean(…),根据beanName获取bean,里面会进行实例化和初始化
  4. 然后的BeanPostProcessor类型的bean,会放入到bean工厂的一个BeanPostProcessor类型的List中(List<BeanPostProcessor> beanPostProcessors
  5. 然后在其他bean进入到初始化阶段时,就是从这个list中取出所有的BeanPostProcessor,进行回调

在这里插入图片描述

AutowireCandidateResolver

AutowireCandidateResolver的作用

AutowireCandidateResolver这个接口是用于对Spring的依赖注入做扩展,Spring在进行依赖注入时,会回调AutowireCandidateResolver这个接口的getSuggestedValue方法,如果返回值不为空,就会为当前bean的这个属性注入getSuggestedValue方法的返回值。

我们可以自己实现一个AutowireCandidateResolver接口的实现类,去处理我们的某些bean的某些特殊属性的依赖注入。

public interface AutowireCandidateResolver {
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>

<span class="token keyword">default</span> <span class="token class-name">Object</span> <span class="token function">getSuggestedValue</span><span class="token punctuation">(</span><span class="token class-name">DependencyDescriptor</span> descriptor<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

一个例子:Spring对@Value注解的处理

比如Spring对于被 @Value注解 的属性的依赖注入,就是通过实现AutowireCandidateResolver接口的来进行处理的。

  1. Spring定义了一个QualifierAnnotationAutowireCandidateResolver类,间接实现了AutowireCandidateResolver,getSuggestedValue方法会读取属性上的@Value注解,返回@Value上的值
  2. 当拿到AutowireCandidateResolver#getSuggestedValue方法的返回值后,Spring会检查是否是String类型,如果是,会交给PlaceholderResolvingStringValueResolver去处理返回值,替换${…}占位符为对应的配置属性或者系统参数
  3. 最后把处理后的value注入到当前bean对应的属性中

PlaceholderResolvingStringValueResolver是Spring的一个专门用来处理${…}占位符的处理器,会解析${…}占位符,替换为与之对应的配置文件属性或者系统参数。

PlaceholderResolvingStringValueResolver实现了StringValueResolver接口,StringValueResolver也是Spring的一个扩展接口,用于对String类型的value做后续的处理,我们也可以实现自己的StringValueResolver定制化我们自己对String类型的value的处理逻辑。
在这里插入图片描述

AutowireCandidateResolver的回调时机

因为AutowireCandidateResolver接口是用于对依赖注入做扩展,所以回调时机自然在依赖注入的时候被回调
在这里插入图片描述

AutowireCandidateResolver的原理

如果我们要实现自己的AutowireCandidateResolver,并且让它在Spring中生效,是要手动放入到DefaultListableBeanFactory中的,可以通过上面的BeanFactoryPostProcessor接口,获取到DefaultListableBeanFactory,然后调用DefaultListableBeanFactory的setAutowireCandidateResolver方法设置我们自己的AutowireCandidateResolver,DefaultListableBeanFactory会把它保存到autowireCandidateResolver属性中。

但是这样就覆盖了Spring原来的AutowireCandidateResolver,如果要让多个AutowireCandidateResolver都有效,可以把原来的AutowireCandidateResolver保存到我们自己的AutowireCandidateResolver中,作为父AutowireCandidateResolver,如果不需要我们处理的属性,可以交给原来的AutowireCandidateResolver做处理。
在这里插入图片描述

当AutowireCandidateResolver放入到DefaultListableBeanFactory之后,在Spring对bean进行依赖注入的时候,会回调AutowireCandidateResolver的getSuggestedValue方法,看是否返回值不为空,如果不为空,就会把返回值注入到属性中,否则就会到容器中寻找匹配的bean注入到属性中

在这里插入图片描述

事件通知机制

Spring事件通知机制的作用

Spring的事件通知机制,是观察者模式的一种实现,在Spring容器初始化的不同节点,发布不同的事件,我们可以实现自己的事件监听器,去监听特定类型的事件,做一些定制化的操作。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
<span class="token keyword">void</span> <span class="token function">onApplicationEvent</span><span class="token punctuation">(</span><span class="token class-name">E</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

一个例子 Dubbo基于Spring事件监听机制实现的服务暴露

Dubbo实现了自己的事件监听器DubboBootstrapApplicationListener,间接实现了ApplicationListener接口,监听的是ContextRefreshedEvent事件类型,Spring在容器刷新完成后会发布该类型的事件

DubboBootstrapApplicationListener监听到该类型的事件后,会获取所有ServiceBean类型的bean,进行服务暴露,开启Netty端口监听,注册服务到ZK上。
在这里插入图片描述

Spring事件监听机制的回调时机

Spring事件监听机制的回调时机,是在Spring容器初始化的不同阶段,都会回调的,光看Spring框架自己的,就有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent四种类型的事件,分别是容器关闭、容器刷新完成、容器启动、容器停止。如果是SpringBoot的话,则事件类型会更多。

我们最熟悉的一般是ContextRefreshedEvent容器刷新完成事件。
在这里插入图片描述

Spring事件监听机制的原理

既然是观察者模式,必须具备三件套:事件发布器、事件监听器、事件

  1. 事件发布器,就是ApplicationEventPublisher接口,而实现该接口的就是我们熟悉的Spring上下文ApplicationContext,正确来说是ApplicationContext接口继承了ApplicationEventPublisher接口,所以Spring上下文本身就具备了发布事件的功能
  2. 事件监听器就是ApplicationListener接口,需要我们自己实现,监听对应的事件类型,进行相应的操作
  3. 事件就是ApplicationEvent接口,它的子接口ApplicationContextEvent就是Spring容器相关的事件,ApplicationContextEvent的实现类就是上面的四种Spring容器相关的事件。在这里插入图片描述

那么很明显,Spring事件监听机制的原理就是观察者模式

  1. 注册事件监听器到事件发布器中(这一步在Spring进行扫描加载bean之前处理)
  2. 在特定的阶段,通过事件发布器发布特定的事件,通知事件监听器
  3. 事件监听器接收到事件,检查事件类型是否与之匹配,与之匹配则处理,否则忽略
    在这里插入图片描述

Lifecycle

Lifecycle的作用

接下来要描述的扩展点是Lifecycle,该接口有两个方法start和stop。

public interface Lifecycle {
<span class="token keyword">void</span> <span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">void</span> <span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">boolean</span> <span class="token function">isRunning</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

实现了该接口的bean,会在Spring容器完成刷新后,回调它的start方法,也就是该bean已经完成初始化被放入到容器后。而stop方法则是在容器关闭时被回调,也就是Spring上下文的close方法被调用的时候。
在这里插入图片描述

一个例子:Eureka的服务端启动入口

Eureka服务端的EurekaServerInitializerConfiguration实现了Lifecycle 的子接口SmartLifecycle,在start方法中就触发Eureka服务端的启动,里面通过EurekaServerBootstrap的contextInitialized方法进行服务端的初始化工作,而EurekaServerBootstrap顾名思义,就是Eureka服务端启动引导器。
在这里插入图片描述

Lifecycle的回调时机

回调时机上面已经说了,Lifecycle#start方法在容器刷新完成后被回调,Lifecycle#stop方法在容器关闭时被回调。
但是Lifecycle#start的回调会发生在Spring发布容器刷新完成事件之前,而Lifecycle#stop,则是在bean销毁之前。
在这里插入图片描述

Lifecycle的原理

Spring对Lifecycle#start方法和Lifecycle#stop的回调,是通过LifecycleProcessor触发的,LifecycleProcessor顾名思义就是Lifecycle处理器

LifecycleProcessor是一个接口,有两个方法onRefreshonClose,LifecycleProcessor的实现类是DefaultLifecycleProcessor

  • LifecycleProcessor的实现类DefaultLifecycleProcessor,在onRefresh方法中,会获取所有实现了Lifecycle接口的bean,回调Lifecycle#start方法
  • 而LifecycleProcessor#onClose方法,则是在容器关闭时,也就是在Spring上下文的close方法里面被回调,回调时机在bean销毁前,里面会回调所有实现了Lifecycle接口的bean的stop方法

Spring会在容器刷新完成后,在发布容器刷新完成事件前,获取到DefaultLifecycleProcessor,回调它的onClose方法。在容器关闭时,在销毁bean之前,会回调DefaultLifecycleProcessor的onClose方法。
在这里插入图片描述

SmartInitializingSingleton

SmartInitializingSingleton的作用

最后一个要描述的扩展点是SmartInitializingSingleton

public interface SmartInitializingSingleton {
<span class="token keyword">void</span> <span class="token function">afterSingletonsInstantiated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5

这个接口是供单例bean实现的,会在所有的单例bean都预加载完成,放入到Spring容器后,Spring会取出所有实现了该接口的单例bean,回调afterSingletonsInstantiated方法,可以在这里再做一些初始化工作。

这个接口可以说相当于是InitializingBean的替代方案

InitializingBean也是Spring提供的一个扩展接口,该接口也有一个作用类似的方法afterPropertiesSet,也可以对实现该接口的bean进行一些初始化操作。

public interface InitializingBean {
<span class="token keyword">void</span> <span class="token function">afterPropertiesSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5

SmartInitializingSingleton与InitializingBean的区别在于作用范围回调时机

  • 作用范围:SmartInitializingSingleton限制了必须是非懒加载的单例bean实现该接口,否则无效,而InitializingBean没有此限制
  • 回调时机:SmartInitializingSingleton#afterSingletonsInstantiated会在所有的单例bean都初始化完成后才会回调,而InitializingBean#afterPropertiesSet则是在该bean完成了属性注入,进入到初始化阶段就会回调,不会等其他的bean初始化完毕(从方法名字也能看出它们的区别)
    在这里插入图片描述

所以如果我们有一些bean的定制化操作是在所有的bean都初始化完成后才能进行的,那么就可以实现SmartInitializingSingleton这个接口。

一个例子:Ribbon基于SmartInitializingSingleton对RestTemplate的定制化

在Ribbon中,就实现了一个SmartInitializingSingleton,在afterSingletonsInstantiated方法里面,会调用它自己实现的一个RestTemplateCustomizer(RestTemplate定制化器),对被@LoadBalanced注解修饰的RestTemplate进行定制化操作。

通过RestTemplateCustomizer#customize方法在RestTemplate的拦截器链里面加入一个拦截器LoadBalancerInterceptor(负载均衡拦截器)。

而LoadBalancerInterceptor又有一个LoadBalancerClient(负载均衡客户端),在LoadBalancerClient里面实现了客户端负载均衡逻辑。

这样RestTemplate就具备了负载均衡的功能,每个通过RestTemplate发出的http请求,都会经过LoadBalancerInterceptor拦截。
在这里插入图片描述

SmartInitializingSingleton的回调时机

这个上面已经说了,SmartInitializingSingleton的afterSingletonsInstantiated方法会在所有的bean都初始化完成后会被回调,严格来说应该是所有非懒加载的单例bean都初始化完成后,因为如果是懒加载或者是非单例,Spring是不会对它进行预加载的。

在这里插入图片描述

SmartInitializingSingleton的原理

  1. 在DefaultListableBeanFactory有一个List<String>类型的属性beanDefinitionNames,里面存放了所有bean的beanName
  2. 在Spring容器初始化的时候,Spring会遍历这个beanDefinitionNames通过getBean(beanName)加载所有的非懒加载的单例bean到单例池中
  3. 在Spring完成所有的单例bean的初始化后,会再次遍历这个beanDefinitionNames,通过DefaultListableBeanFactory#getSingleton方法,从单例池中取出每一个加载好的bean
  4. 然后判断该bean是否实现SmartInitializingSingleton接口,如果是,会回调它的afterSingletonsInstantiated方法
    在这里插入图片描述

总结

以上就是本文对Spring扩展点的所有介绍,基本上已经涵盖了Spring里面比较重要的扩展点,当然还有其他的一些扩展点没有介绍到,但是由于篇幅关系,就不全部罗列了。

总结性的文字就不多说了,因为本文的字已经够多的了,这里就放两张图,供大家细品吧。

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值