简单易懂的Spring扩展点详细解析,看不懂你来打我

news2024/9/27 15:21:05

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

  • 总览
  • BeanFactoryPostprocessor
    • 作用
    • 回调时机
    • 原理
  • BeanDefinitionRegistryPostProcessor
    • 作用
      • 例子:Spring对@Configuration、@ComponentScan、@Component、@Bean、@Import等注解的处理
    • 回调时机
    • 原理
  • ImportSelector
    • 作用
      • 例子:SpringBoot的自动装配机制
    • 回调时机
    • 原理
  • ImportBeanDefinitionRegistrar
    • 作用
      • 例子一:Mybatis与Spring整合,Mapper的扫描和加载
        • FactoryBean\<T\>
      • 例子二:OpenFeign扫描@FeignClient注解生成Feign客户端
    • 回调时机
    • 原理
  • BeanPostProcessor
    • 作用
      • 例子一:Spring AOP的入口
      • 例子二:@PostConstruct注解修饰方法的回调
    • 回调时机
    • 原理
  • AutowireCandidateResolver
    • 作用
      • 例子:Spring对@Value注解的处理
    • 回调时机
    • 原理
  • 事件通知机制
    • 作用
      • 例子 Dubbo基于Spring事件监听机制实现的服务暴露
    • 回调时机
    • 原理
  • Lifecycle
    • 作用
      • 例子:Eureka的服务端启动入口
    • 回调时机
    • 原理
  • SmartInitializingSingleton
    • 作用
      • 例子:Ribbon基于SmartInitializingSingleton对RestTemplate的定制化
    • 回调时机
    • 原理
  • 总结

总览

这里罗列的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的扩展点,所以扩展点也抽离出去了,等下面讲到某个扩展点的时候,再把它插入进去。
在这里插入图片描述

BeanFactoryPostprocessor

作用

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

@FunctionalInterface
public interface BeanFactoryPostProcessor {

	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

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

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

在这里插入图片描述

回调时机

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

原理

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其实也属于BeanFactoryPostProcessor,它直接继承了BeanFactoryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

可以看到它的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 因为是BeanFactoryPostProcessor的子类,所以回调时机很明显跟BeanFactoryPostProcessor是一样的,但是严格来说,应该是在BeanFactoryPostProcessor的之前回调。
在这里插入图片描述

原理

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

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

在这里插入图片描述

ImportSelector

作用

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

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

public interface ImportSelector {

	String[] selectImports(AnnotationMetadata importingClassMetadata);

	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}

}

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

原理

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

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

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

在这里插入图片描述

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

ImportBeanDefinitionRegistrar

作用

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

public interface ImportBeanDefinitionRegistrar {

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {

		registerBeanDefinitions(importingClassMetadata, registry);
	}
	
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}

}

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> {

	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
	
	T getObject() throws Exception;

	Class<?> getObjectType();

	default boolean isSingleton() { return true; }

}

比如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也是要通过@Import注解导入的,所以回调时机自然也是和ImportSelector一样。
在这里插入图片描述

原理

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

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

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

在这里插入图片描述

BeanPostProcessor

作用

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

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

public interface BeanPostProcessor {

	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

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

原理

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

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

public interface AutowireCandidateResolver {
	
	...
	
	default Object getSuggestedValue(DependencyDescriptor descriptor) {
		return null;
	}
	
	...
	
}

例子: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,并且让它在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容器初始化的不同节点,发布不同的事件,我们可以实现自己的事件监听器,去监听特定类型的事件,做一些定制化的操作。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	void onApplicationEvent(E event);

}

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

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

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

回调时机

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

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

原理

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

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

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

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

Lifecycle

作用

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

public interface Lifecycle {

	void start();

	void stop();

	boolean isRunning();

}

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

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

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

回调时机

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

原理

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

public interface SmartInitializingSingleton {

	void afterSingletonsInstantiated();

}

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

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

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

public interface InitializingBean {

	void afterPropertiesSet() throws Exception;

}

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的afterSingletonsInstantiated方法会在所有的bean都初始化完成后会被回调,严格来说应该是所有非懒加载的单例bean都初始化完成后,因为如果是懒加载或者是非单例,Spring是不会对它进行预加载的。

在这里插入图片描述

原理

  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里面比较重要的扩展点,当然还有其他的一些扩展点没有介绍到,但是由于篇幅关系,就不全部罗列了。

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

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/139895.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue组件之间的数据传递

目录 组件之间的通信 1、组件之间的关系&#xff1a;父子关系、兄弟关系、跨级关系 2、父子组件之间的通信&#xff08;数据传递&#xff09;&#xff1a; 3、兄弟组件之间的通信&#xff08;数据传输&#xff09;&#xff1a; 4、跨级组件之间的通信&#xff1a;provide /…

SparkSQL 入门简介

在安装目录的bin目录打开spark-shell.cmd&#xff0c;输入如下&#xff0c;按tab健&#xff0c;查看可执行数据源 spark.read.在安装目录D:\spark-3.0.0-bin-hadoop3.2\bin\input新建user.json {"username":"chen","age":3} {"username&q…

vue全家桶-vuex(一)

vue全家桶-vuex&#xff08;一&#xff09;1.Vuex概述2.Vuex中的核心特性A.State1.this.$store.state.全局数据名称-组件访问State中的数据的第一种方式2.组件访问State中的数据的第二种方式&#xff1a;按需导入B.Mutation1.this.$store.commit是触发Mutation的第一种方式2.触…

java学习day67(乐友商城)商品详情及静态化(Thymeleaf)

1.商品详情 当用户搜索到商品&#xff0c;肯定会点击查看&#xff0c;就会进入商品详情页&#xff0c;接下来我们完成商品详情页的展示&#xff0c; 1.1.Thymeleaf 在商品详情页中&#xff0c;我们会使用到Thymeleaf来渲染页面&#xff0c;所以需要先了解Thymeleaf的语法。 …

简明Java讲义 1:Java环境搭建与入门

目录 1、Java 介绍 2、Java 运行机制 3、Java开发环境搭建 4、Hello World&#xff01; 1、Java 介绍 Java是Sun微系统公司在1995年推出的&#xff0c;是一门面向对象的编程语言 2006年12月&#xff0c;Sun公司发布了JDK1.6&#xff08;也称作Java SE 6&#xff09; 200…

数位DP~

综述 数位DP的应用范围&#xff1a; 在某个区间内有多少个满足一定的性质 数位DP中使用的方法&#xff1a; 类似于前缀和。A到B相当于f[B] - a[A-1] 这一点尤为重要&#xff0c;因为已经弱化了边界&#xff0c;使得考虑的更少分情况讨论 ​ 1081. 度的数量 ​ 输入样例…

BGP综合实验

目录 1.拓扑图 2.实验要求 3.实验思路 4.主要配置 5.测试 6.实验总结 1.拓扑图 2.实验要求 AS1存在两个环回&#xff0c;一个地址为192.168.1.0/24&#xff1b;AS3中存在两个环回&#xff0c;一个为192.168.2.0/24&#xff1b;整个AS2的IP地址为172.16.0.0/16&#xff0c…

k8s之工作机制

写在前面 本文一起看下k8s基本架构。 1&#xff1a;Kubernetes的基本架构 k8s本身也是一种分布式架构&#xff0c;也需要在多台机器&#xff08;实体机或虚拟机无差别&#xff09;部署&#xff0c;部署的机器我们叫做节点&#xff0c;其中节点分为Master node即主节点,worke…

java使用反射给对象属性赋值

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3;哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步&#x1f91d;&#x1f91d;一位上进心十足的【Java ToB端大厂领…

高频js手写题之实现数组扁平化、深拷贝、总线模式

前言 古人学问无遗力&#xff0c;少壮工夫老始成。纸上得来终觉浅&#xff0c;绝知此事要躬行。看懂一道算法题很快,但我们必须将这道题的思路理清、手写出来。 三道js手写题的思路和代码实现 数组扁平化 演示效果 将[1, [1, 2], [1, [2]]] 变成 [1, 1, 2, 1, 2] 第一种&…

抽象类与抽象方法

文章目录一、abstract关键字使用修饰类&#xff1a;抽象类修饰方法&#xff1a;抽象方法注意点抽象类的匿名子类一、abstract关键字使用 abstract&#xff1a;抽象的 可以修饰&#xff1a;类、方法 修饰类&#xff1a;抽象类 1、此类不可进行实例化 2、抽象类中一定有构造器…

报错 cannot import name ‘int‘ from ‘numpy‘

报错详情&#xff1a; 原因是因为np.int在numpy1.20已经被废弃掉了&#xff0c;可以通过 pip show numpy在命令行里查看。 现在使用的是np.int_ 或者 np.int32 或者 np.int64 猜测原因 但这个报错是在我自己的site-packages里的numpy的报错&#xff0c;我怀疑可能是numpy本身…

【linux】crontab

文章目录crontab简介crontab安装语法实例脚本无法执行问题常用的命令展示crontab的注意事项来源crontab简介 crontab命令常见于Unix和类Unix的操作系统之中&#xff0c;用于设置周期性被执行的指令。该命令从标准输入设备读取指令&#xff0c;并将其存放于“crontab”文件中&a…

linux系统中CAN驱动的通信方法与原理

大家好&#xff0c;今天主要和大家分享一下&#xff0c;如何使用linux系统下的CAN驱动实验。 目录 第一&#xff1a;CAN通信基本简介 第二&#xff1a;CAN通信的主要特点 第三&#xff1a;CAN通信协议 第四&#xff1a;程序代码的具体实现 第五&#xff1a;使能Linux内核自…

MATLAB-ezplot绘图函数

ezplot 函数与fplot 函数类似&#xff0c;该函数可以绘制显函数图形、隐函数图形和参数方程图形。ezplot函数的调用格式如下。 ezplot(f) ezplot(f, [ min , max ]) ezplot(f.[ xmin , xmax , ymin , ymax]) ezplot(x,y) ezplot(x,y , [tmin , tmax]) ezplot(.. . ,f…

jvm内存管理

参考链接 参考链接 Garbage Collection Concepts garbage collector的作用包括&#xff1a; 分配内存确定活着的对象不被清理回收死了的对象占用的内存 寻找和释放垃圾占用的内存空间的过程称为garbage collection一般情况下&#xff0c;整个堆或堆的一部分被填满时或者达到…

C++11 多线程

线程&#xff08;thread&#xff09;是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程中&#xff0c;是进程中的实际运作单位&#xff0c;一条线程指的是进程中一个单一顺序的控制流&#xff0c;一个进程可以并发多个线程&#xff0c;每条线程执行不同的任务。…

FreeRTOS教程——定时器(二)

Free RTOS定时器 一、概念 一、概论 软件定时器允许设置一段时间&#xff0c;当设置的时间到达之后就执行指定的功能函数&#xff0c;被定时器 调用的这个功能函数叫做定时器的回调函数。回调函数的两次执行间隔叫做定时器的定时周期&#xff0c; 简而言之&#xff0c;当定时…

switch分支结构

一. 简介switch结合case&#xff0c;能够判断一个变量或表达式与一系列值中的某个值是否相等&#xff0c;这里的每个值都被称为一个分支。switch语句在执行时&#xff0c;会先进行值的匹配&#xff0c;匹配成功时会进入到对应case语句。再根据是否有 break语句&#xff0c;判断…

手把手教你正确地创建并配置一个SpringBoot项目

文章目录1. 安装Spring Boot Helper插件2. 创建SpringBoot项目3. 配置SpringBoot项目4. 选择修改配置&#xff08;选做&#xff09;4.1 修改端口号4.2 其他自定义配置5. SpringBoot热部署本文主要是针对IDEA社区版用户的&#xff0c;如果你是专业版的用户&#xff0c;那么是可以…