片面解析-Springboot裁减点之BeanDefinitionRegistryPostProcessor-轻松把握 (片面理解是什么意思)
前言
经过这篇文章来大家分享一下,另外一个Springboot的裁减点BeanDefinitionRegistryPostProcessor,普通称这类裁减点为容器级后置处置器,另外一类是Bean级的后置处置器;容器级的后置处置器会在Spring容器初始化后、刷新前这个期间口头一次性,Bean级的后置处置器,则是在每一个Bean实例化前后都会口头。
性能特性
publicinterfaceBeanDefinitionRegistryPostProcessorextendsBeanFactoryPostProcessor{voidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry)throwsBeansException;}
@FunctionalInterfacepublicinterfaceBeanFactoryPostProcessor{voidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException;}
总结起来就是,在一切的BeanDefinition加载成功之后,Bean真正被实例化之前,可以经过成功BeanDefinitionRegistryPostProcessor接口,对BeanDefinition再做一些定制化的操作,比如修正某个bean的BeanDefinition的属性、手动注册一些复杂的Bean。
关于Spring原理不太相熟的小同伴心里看到这或者有点晕了,BeanDefinition是什么?BeanDefinitionRegistry又是什么?ConfigurableListableBeanFactory又又是什么?别着急,这里拐个弯繁难的解释一下,繁难上方的内容了解起来更顺畅。
BeanDefinition
大家都知道,Spring的外围之一是IOC(控制反转),Spring之所以可以成功bean控制权的反转,是由于Spring的容器性能,在bean归入Spring容器治理前,一切bean会被形象封装成一个BeanDefinition实例,而后会在不同的机遇依据BeanDefinition实例消息对bean启动实例化。
繁难说,Dog.形容狗这一类生物的属性和行为,BeanDefinition形容Dog.java这个类。
BeanDefinitionRegistry
BeanDefinitionRegistry从字面意思看是bean的定义消息的注册注销,其实这个类的性能和字面意思一样,就是对BeanDefinition启动治理(增删改查);
publicinterfaceBeanDefinitionRegistryextendsAliasRegistry{//注册beanDefinitionvoidregisterBeanDefinition(StringbeanName,BeanDefinitionbeanDefinition)throwsBeanDefinitionStoreException;//移除指定的beanDefinitionvoidremoveBeanDefinition(StringbeanName)throwsNoSuchBeanDefinitionException;//依据beanName查问beanDefinitionBeanDefinitiongetBeanDefinition(StringbeanName)throwsNoSuchBeanDefinitionException;//判别某个beanDefinition能否曾经注册booleancontnsBeanDefinition(StringbeanName);//失掉一切已注册的beanDefinitionString[]getBeanDefinitionNames();//失掉一切已注册的beanDefinition的数量intgetBeanDefinitionCount();//判别某个beanDefinition能否曾经被经常使用booleanisBeanNameInUse(StringbeanName);}
ConfigurableListableBeanFactory
上方提到了Spring的容器,Spring的外围之一是IOC,那么Spring的容器设计就是外围中的外围了。Spring的容器有多种外形,最基础的外形就是BeanFactory,ConfigurableListableBeanFactory直接承袭了BeanFactory,因此ConfigurableListableBeanFactory成功类除了有Spring基础版本容器的性能外,还有一些初级的性能,Springboot自动的实践成功是DefaultListableBeanFactory,有兴味的小同伴可以以此为入口深化摸索一番,这里不开展细说了。
自定义成功
MyBeanDefinitionRegistryPostProcessor
上方经过一个详细类MyBeanDefinitionRegistryPostProcessor成功BeanDefinitionRegistryPostProcessor接口,来摸索BeanDefinitionRegistryPostProcessor成功类的初始化和口头环节。
@DatapublicclassDog{privateStringname;privateStringcolor;}
@ComponentpublicclassMyBeanDefinitionRegistryPostProcessorimplementsBeanDefinitionRegistryPostProcessor{@OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry)throwsBeansException{//手工定义一个beanDefinition实例RootBeanDefinitionbeanDefinition=newRootBeanDefinition();//给beanDefinition填充属性beanDefinition.setBeanClass(Dog.class);MutablePropertyValuespropertyValues=newMutablePropertyValues();PropertyValuepropertyValue1=newPropertyValue("name","旺财");PropertyValuepropertyValue2=newPropertyValue("color","彩色");propertyValues.addPropertyValue(propertyValue1);propertyValues.addPropertyValue(propertyValue2);beanDefinition.setPropertyValues(propertyValues);//注册手工定义的beanDefinitionregistry.registerBeanDefinition("dog",beanDefinition);}@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException{System.out.println("-----------start------------");//依据类名取出手工注册的beanDefinitionBeanDefinitionbeanDefinition=beanFactory.getBeanDefinition("dog");System.out.println(beanDefinition.getBeanClassName());//依据类冷静器中取出手工注册的beanDefinition所形容的实例beanDogdog=beanFactory.getBean(Dog.class);System.out.println(dog.getName());System.out.println(dog.getColor());System.out.println("-----------end------------");}}
单元测试
@Testpublicvoidtest(){AnnotationConfiglicationContextcontext=newAnnotationConfigApplicationContext("com.fanfu");Dogdog=((Dog)context.getBean("dog"));System.out.println(dog.getName());System.out.println(dog.getColor());}
UML类图
经过BeanDefinitionRegistryPostProcessorUML类图可以看出BeanDefinitionRegistryPostProcessor承袭了BeanFactoryPostProcessor,postProcessBeanDefinitionRegistry()方法属于BeanDefinitionRegistryPostProcessor,postProcessBeanFactory()属于BeanFactoryPostProcessor,一切成功了BeanDefinitionRegistryPostProcessor接口的成功类都须要成功这个方法,而作为Springboot的裁减点之一,其裁减的逻辑也在这两个方法中;
初始化和口头机遇
经过自定义的MyBeanDefinitionRegistryPostProcessor类,成功BeanDefinitionRegistryPostProcessor接口,从名目启动开局,其口头环节如下:
上方是我依据整个调用环节画的一个时序图,环节确实比拟复杂,然而逻辑比拟明晰,因此并不难了解,想要真的搞分明整个环节,最好的方法就是照着这个图,亲身口头一遍,经过debug观察每一个关键节点的口头环节。
外部成功类
spring-boot-starter-web中内置的成功类有CachingMetadataReaderFactoryPostProcessor、ConfigurationClassPostProcessor、ConfigurationWarningsPostProcessor、EmbeddedDataSourceBeanFactoryPostProcessor、ImportsCleanupPostProcessor、TestRestTemplateRegistrar、WebTestClientRegistrar、WsdlDefinitionBeanFactoryPostProcessor,观察一下每个成功类会发现:都比拟相似,这些内置成功类都是Springboot中的外部类,经过这些BeanDefinitionRegistryPostProcessor外部成功类向Spring容器中注册了一些不凡的BeanDefinition,假设开展详细再说一说这些Bean,怕是一天一夜也说不完,有兴味的小同伴可以深化了解一下,这里就不再开展了。
总结
经过梳理整个环节,其实最关键的就是一句话:在Spring容器初始后、未刷新前,即Bean已被扫描注册为BeanDefinition后,未正式实例化前,可以经过成功BeanDefinitionRegistryPostProcessor做一些额外的操作。
Dubbo: 在springboot中的启动过程
SpringBoot在启动时,通过完成对依赖jar包中XxAutopConfiguration类的注册,自然DubboAutoConfiguration也会被注册到容器内部。 DubboAutoConfiguration中,定义了一个ServiceClassPostProcessor ,同样会被注册到容器内。 ServiceClassPostProcessor 实现了BeanDefinitionRegistryPostProcessor接口,同样他也是一个BeanFactoryPostProcessor。 在SpringBoot刷新容器,调用所有BeanFactoryPostProcessors时,对BeanDefinitionRegistryPostProcessor,会去调用其postProcessBeanDefinitionRegistry方法。 至此,便将ServiceBean注册进了Spring IOC容器。 至于对象和代理对象的创建,那是后话了。 我们知道DubboService会被注册到注册中心,最终的结果是:将服务名、服务对外暴露的url等信息通过网络请求发送到注册中心,那么对外暴露的时机是什么时候?它又是如何做到这件事情的? 关于时机,它应该在容器刷新完成之后将所有DubboService对外暴露,那么如何感知到容器刷新呢?SpringBoot中可以注册listener,容器开始启动、启动完成等事件会通知注册进来的listener。 ServiceClassPostProcessor 除了向容器注册ServiceBean之外,还注册了一个监听器:DubboBootstrapApplicationListener,当感知到容器刷新完成和关闭事件时,做出相应处理,在这里关注刷新完成该如何处理。 启动DubboBootStrap 至于Service如何暴露:如下图最后服务暴露到底对外暴露了个啥呢?其实就是invoker的url,当consumer发起服务调用的时候,发起请求,当provider接收到请求之后,将请求携带的信息(接口、方法名、参数数组...)封装为Invocation对象,分派到相应的invoker,通过(invocation)完成调用。 另外敖丙的文章也还不错, Dubbo系列之服务暴露过程 从springboot解析@DubboReference开始讲起。 DubboAutoConfiguration被@EnableDubboConfig注解,通过该注解引入了DubboConfigConfigurationRegistrar类。 DubboConfigConfigurationRegistrar::registerBeanDefinitions(args) ReferenceAnnotationBeanPostProcessor是一个InstantiationAwareBeanPostProcessorAdapter,在对象实例化后,填充属性中,会调用其postProcessPropertyValues 最终到达()方法,返回代理对象,并注入到目标对象的field。 如果抛开SpringBoot如何注入被@DubboReference注解的Bean,可以说()就是服务引用的入口。 后面的事情就是Dubbo内部要完成的了,大概过程如这样:
[Spring boot源码解析] 2 启动流程分析
在了解 Spring Boot 的启动流程的时候,我们先看一下一个Spring Boot 应用是如何启动的,如下是一个简单的 SpringBoot 程序,非常的简洁,他是如何做到的呢,我们接下来就将一步步分解。 我们追踪()方法,其实最终它主要的逻辑是新建一个SpringApplication ,然后调用他的 run 方法,如下: 我们先来看一下创建SpringApplication的方法: 在将Main class 设置 primarySources后,调用了()方法,该方法是为了检查当前的应用类型,并设置给webApplicationType 。 我们进入deduceFromClasspath方法 : 这里主要是通过类加载器判断是否存在REACTIVE相关的类信息,假如有就代表是一个REACTIVE 的应用,假如不是就检查是否存在Servelt 和 ConfigurableWebApplicationContext,假如都没有,就代表应用为非 WEB 类应用,返回NONE ,默认返回SERVLET类型,我们这期以我们目前最常使用的SERVLET类型进行讲解,所以我们在应用中引入了spring-boot-starter-web作为依赖: 他会包含 Spring-mvc 的依赖,所以就包含了内嵌 tomcat 中的Servlet和 Spring-web 中的ConfigurableWebApplicationContext ,因此返回了SERVLET类型。 回到刚才创建SpringApplication的构建方法中,我们设置完成应用类型后,就寻找所有的Initializer实现类,并设置到 SpringApplication的Initializers中,这里先说一下getSpringFactoriesInstances方法,我们知道在我们使用 SpringBoot 程序中,会经常在META-INF/目录下看到一些 EnableAutoConfiguration ,来出发 config 类注入到容器中,我们知道一般一个 config 类要想被 SpringBoot 扫描到需要使用@CompnentScan来扫描具体的路径,对于 jar 包来说这无疑是非常不方便的,所以 SpringBoot 提供了另外一种方式来实现,就是使用,比如下面这个,我们从 Springboot-test 中找到的例子,这里先定义了一个ExampleAutoConfiguration,并加上了 Configuration注解: 然后在 中定义如下: 那这种方式是怎么实现的你,这就要回到我们刚才的方法 getSpringFactoriesInstances: 我们先来看一下传入参数,这里需要注意的是 args,这个是初始化对应 type 的时候传入的构造参数,我们先看一下SpringFactoriesLoader#loadFactoryNames 方法: 首先是会先检查缓存,假如缓存中存在就直接返回,假如没有就调用classLoader#getResources方法,传入 META-INF/ ,即获取所有 jar 包下的对应文件,并封装成 UrlResource ,然后使用PropertiesLoaderUtils 将这些信息读取成一个对一对的 properties,我们观察一下都是按 properties 格式排版的,假如有多个就用逗号隔开,所以这里还需要将逗号的多个类分隔开来,并加到 result 中,由于 result 是一个LinkedMultiValueMap类型,支持多个值插入,最后放回缓存中。 最终完成加载META-INF/中的配置,如下:我们可以看一下我们找到的initializer有多少个:在获取到所有的Initializer后接下来是调用createSpringFactoriesInstances 方法进行初始化。 这里的 names 就是我们上面通过类加载器加载到的类名,到这里会先通过反射生成class对象,然后判断该类是否继承与ApplicationContextInitializer,最后通过发射的方式获取这个类的构造方法,并调用该构造方法,传入已经定义好的构造参数,对于ApplicationContextInitializer 是无参的构造方法,然后初始化实例并返回,回到原来的方法,这里会先对所有的ApplicationContextInitializer 进行排序,调用AnnotationAwareOrderComparator#sort(instances)方法,这里就是根据@Order 中的顺序进行排序。 接下来是设置ApplicationListener ,我们跟进去就会发现这里和上面获取ApplicationContextInitializer的方法如出一辙,最终会加载到如图的 15 个listener(这里除了 EnableEncryptablePropertiesBeanFactoryPostProcessor 外,其他都是 SpringBoot 内部的 Listener):在完成SpringApplication对象的初始化后,我们进入了他的 run 方法,这个方法几乎涵盖了SpringBoot生命周期的所有内容,主要分为九个步骤,每一个步骤这里都使用注解进行标识: 主要步骤如下: 第一步:获取 SpringApplicationRunListener, 然后调用他的 staring 方法启动监听器。 第二步:根据 SpringApplicationRunListeners以及参数来准备环境。 第三步:创建 Spring 容器。 第四步:Spring 容器的前置处理。 第五步:刷新 Spring 容器。 第六步: Spring 容器的后置处理器。 第七步:通知所有 listener 结束启动。 第八步:调用所有 runner 的 run 方法。 第九步:通知所有 listener running 事件。 我们接下来一一讲解这些内容。 我们首先看一下第一步,获取SpringApplicationRunListener : 这里和上面获取initializer和listener的方式基本一致,都是通过getSpringFactoriesInstances ,最终只找到一个类就是: ,然后调用其构造方法并传入产生args, 和SpringApplication本身: 我们先看一下构造函数,首先将我们获取到的 ApplicationListener集合添加到initialMulticaster 中, 最后都是通过操作 SimpleApplicationEventMulticaster来进行广播,我,他继承于AbstractApplicationEventMulticaster ,我们先看一下他的addApplicationListener方法: 我们可以看出,最后是放到了 applicationListenters这个容器中。 他是defaultRetriever的成员属性,defaultRetriever 则是 AbstractApplicationEventMulticaster的私有类,我们简单看一下这个类: 我们只需要看一下这里的getApplicationListeners方法,它主要是到beanFactory中检查是否存在多的ApplicationListener 和旧的 applicationListeners 组合并返回,接着执行listener的start方法,最后也是调用了AbstractApplicationEventMulticaster的 multicastEvent查找支持对应的 ApplicationEvent 类型的通知的 ApplicationListener 的 onApplicationEvent方法 ,这里除了会: 筛选的方法如下,都是调用了对应类型的 supportsEventType方法 : 如图,我们可以看到对 感兴趣的有5个Listener 环境准备的具体方法如下: 首先是调用getOrCreateEnvironment方法来创建 environment ,我们跟进去可以发现这里是根据我们上面设置的环境的类型来进行选择的,当前环境会创建StandardServletEnvironment 我们先来看一下StandardServletEnvironment的类继承关系图,我们可以看出他是继承了AbstractEnvironment:他会调用子类的customizePropertySources方法实现,首先是StandardServletEnvironment 的实现如下,他会添加servletConfigInitParams ,servletContextInitParams ,jndiProperties三种 properties,当前调试环境没有配置 jndi properties,所以这里不会添加。 接着调用父类的customizePropertySources 方法,即调用到了StandardEnvironment。 我们看一下StandardEnvironment#customizePropertySources 方法,与上面的三个 properties 创建不同,这两个是会进行赋值的,包括系统环境变量放入systemEnvironment中,jvm 先关参数放到systemProperties中: 这里会添加 systemEnvironment和systemProperties这两个 properties,最终拿到的 properties 数量如下 4个:在创建完成Environment后,接下来就到了调用configureEnvironment方法: 我们先看一下configurePropertySources方法,这里主要分两部分,首先是查询当前是否存在defaultProperties,假如不为空就会添加到environment的propertySources中,接着是处理命令行参数,将命令行参数作为一个CompositePropertySource或则SimpleCommandLinePropertySource添加到 environment的propertySources里面, 接着调用ConfigurationPropertySources#attach方法,他会先去environment 中查找configurationProperties, 假如寻找到了,先检查configurationProperties 和当前environment 是否匹配,假如不相等,就先去除,最后添加configurationProperties并将其sources属性设置进去。 回到我们的prepareEnvironment逻辑,下一步是通知观察者,发送ApplicationEnvironmentPreparedEvent事件,调用的是SpringApplicationRunListeners#environmentPrepared方法,最终回到了SimpleApplicationEventMulticaster#multicastEvent方法,我们通过 debug 找到最后对这个时间感兴趣的Listener如下:其主要逻辑如下: 这个方法最后加载了 PropertySourceLoader, 这里主要是两种,一个是用于 Properties 的,一个是用于 YAML 的如下:其中 apply 方法主要是加载defaultProperties,假如已经存在,就进行替换,而替换的目标PropertySource就是load 这里最后的一个consumer函数加载出来的,这里列一下主要做的事情: 1、加载系统中设置的所有的Profile。 2、遍历所有的Profile ,假如是默认的Profile , 就将这个 Profile 加到 environment 中。 3、调用load 方法,加载配置,我们深入看一下这个方法: 他会先调用 getSearchLocations 方法,加载所有的需要加载的路径,最终有如下路径:其核心方法是遍历所有的propertySourceLoader ,也就是上面加载到两种 propertySourceLoader ,最红 loadForFileExtension 方法,加载配置文件,这里就不展开分析了,说一下主要的作用,因为每个 propertySourceLoader都有自己可以加载的扩展名,默认扩展名有如下四个 properties, xml, yml, yaml,所以最终拿到文件名字,然后通过-拼接所有的真实的名字,然后加上路径一起加载。 接下来,我们分析BackgroundPreinitializer ,这个方法在接收ApplicationPrepareEnvironment事件的时候真正调用了这份方法: 1、 ConversionServiceInitializer主要负责将包括 日期,货币等一些默认的转换器注册到formatterRegistry中。 2、 ValidationInitializer创建 validation 的匹配器。 3、 MessageConverterInitializer主要是添加了一些 http 的 Message Converter。 4、 JacksonInitializer主要用于生成 xml 转换器的。 接着回到我们将的主体方法, prepareEnvironment在调用完成(environment)方法后,调用bindToSpringApplication(environment)方法,将 environment 绑定到SpirngApplication中。 接着将enviroment转化为StandardEnvironment对象。 最后将configurationProperties加入到 enviroment中, configurationProperties 其实是将 environment 中其他的PropertySource重新包装了一遍,并放到 environment 中,这里主要的作用是方便 PropertySourcesPropertyResolver 进行解析。 它主要是检查是否存在配置,这个配置的主要作用是设置 javaBean 的内省模式,所谓内省就是应用程序在 Runtime 的时候能检查对象类型的能力,通常也可以称作运行时类型检查,区别于反射主要用于修改类属性,内省主要用户获取类属性。 那么我们什么时候会使用到内省呢,java主要是通过内省工具 Introspector 来完成内省的工作,内省的结果通过一个 Beaninfo 对象返回,主要包括类的一些相关信息,而在 Spring中,主要是BeanUtils#copyProperties 会使用到,Spring 对内省机制还进行了改进,有三种内省模式,如下图中红色框框的内容,默认情况下是使用 USE_ALL_BEANINFO。 假如设置为true,就是改成第三中 IGNORE_ALL_BEANINFO首先是检查 Application的类型,然后获取对应的 ApplicationContext 类,我们这里是获取到了接着调用(contextClass);方法进行对象的初始化。 最终其实是调用了AnnotationConfigServletWebServerApplicationContext的默认构造方法。 我们看一下这个方法做了什么事情。 这里只是简单的设置了一个 reader 和一个 scanner,作用于 bean 的扫描工作。 我们再来看一下这个类的继承关系这里获取ExceptionReporter的方式主要还是和之前Listener的方式一致,通过getSpringFactoriesInstances来获取所有的SpringBootExceptionReporter 。 其主要方法执行如下:
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。