Java开发从工作到原理--AutoConfiguration类加载方式讲解

Java开发从工作到原理--SpringBoot如何启动内置Tomcat中,我们提到TomcatServletWebServerFactory对象是有由ServletWebServerFactoryAutoConfiguration自动配置类通过Import注解引入的,但是ServletWebServerFactoryAutoConfiguration自己是以什么样的形式,在什么时候被加载的呢?

在spring-boot-autoconfigure包中,有很多组件的自动装配类,类名基本上都以AutoConfiguration结尾,且在类上标有Configuration注解,但这些类并不是通过Spring的自动扫描机制扫描加载的,为什么会这么说呢,因为Configuration注解并非元注解,它是继承Component注解而来的,SpringBoot配置了专门的Component注解处理器对标有Component注解及继承Component的注解的类进行加载处理。这个扫描处理器就是ClassPathBeanDefinitionScanner,它是由SpringApplication类在项目启动时,为实例化AnnotationConfigServletWebServerApplicationContext调用其构造函数时被实例化的。但是ClassPathBeanDefinitionScanner的扫描路径是由启动类的位置确定的,一般为启动类所在包的所有子包及类文件。

DemoApplicatiion所在com.example.demo下的所有东西都会被扫描

加入我们在com.example包下也建一个controller,并将TestController.java文件复制过去,修改TestController所在包名后run DemoApplication类,程序同样能正常启动,没有报错。

正常情况下,两个TestController由BeanNameGenerator生成的beanName应该都是testController

Spring的BeanFactory中,出现相同beanName的两个bean时,在启动时会报异常!

完整的异常信息为:

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.example.demo.DemoApplication]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'testController' for bean class [com.example.demo.controller.TestController] conflicts with existing, non-compatible bean definition of same name and class [com.example.demo.config.TestController]
	
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'testController' for bean class [com.example.demo.controller.TestController] conflicts with existing, non-compatible bean definition of same name and class [com.example.demo.config.TestController]

config包下的TestController先被扫描加载,再来加载controller包下的TestController时就报了异常。

AutoConfiguration类并未在扫描路径中,就像com.example.controller下的TestController,那么这些AutoConfiguration类是怎么被加载的呢?

这里就要说一下Spring中的一个扩展机制了,这个扩展机制类似jdk的SPI,跟Dubbo的SPI机制也有点相似,就是在文件中配置接口的实现类,然后使用特殊的工具获取这些类的信息,然后实例化。

spring的扩展机制中文件名字是固定的,spring.factories,存放在jar包的META-INFO目录下,文件中会以键值对的形式进行信息存放,接口为key,实现类为value,多个value之间用逗号隔开,使用SpringFactoriesLoader工具类根据接口类型进行获取。

AutoConfiguration类对应的类型为EnableAutoConfiguration

JDK的SPI机制中文件名就是接口名称,存放在META-IFNO/services目录下,文件中的实现类信息就是指定使用的默认实现,通过ServiceLoader工具类获取,所有的实现类都会被加载实例化。

Dubbo的SPI机制中文件名也是接口名称,存放在META-INFO/dubbo.internal目录下,文件中会以键值对的形式进行信息存放,key为实现方式名称,value为具体实现类,使用ExtensionLoader工具类进行获取。

那么反向追踪SpringFactoriesLoader中方法的Callers可知道AutoConfiguration类是何时被加载的,有传入指定类型的只有loadFactoryNames方法和loadFactories方法

loadFactories方法Callers中没有传入类型是EnableAutoConfiguration类的,

loadFactoryNames方法中,只有AutoConfigurationImportSelector.getCandidateConfigurations方法传入的是EnableAutoConfiguration类

getCandidateConfigurations方法Callers为:

一个还是在AutoConfigurationImportSelector内,一个在ImportAutoConfigurationImportSelector内,而ImportAutoConfigurationImportSelector是AutoConfigurationImportSelector的子类。

查看AutoConfigurationImportSelector的类注释信息,可以看到

进而继续查看EnableAutoConfiguration注解可以看到AutoConfigurationImportSelector是由EnableAutoConfiguration通过Import注解引入。

而EnableAutoConfiguration注解我们非常熟悉,SpringBootApplication注解继承了EnableAutoConfiguration注解。

再查看ImportAutoConfigurationImportSelector类注释信息可以看到

进而继续查看ImportAutoConfiguration可以看到ImportAutoConfigurationImportSelector是由ImportAutoConfiguration通过Import注解引入。

那么到底AutoConfiguration是通过谁引入的呢?我们在getCandidateConfigurations方法内打断点,然后debug模式启动来看个究竟。

确定为AutoConfigurationImportSelector中调用,且更深的方法调用栈也都一清二楚。

顺着方法栈解释一下各个方法的作用:

AutoConfigurationImportSelector.getAutoConfigurationEntry 方法

1、判断了spring.boot.enableautoconfiguration属性是否为true(未配置则默认为true)如果为false则直接返回EMPTY_ENTRY。

2、从注解元数据中解析出注解属性信息,exclude属性值和excludeName属性值。

3、从spring.factories中读取所有AutoConfiguration类信息。

4、去除从spring.factories中读取到信息的重复项。

5、确定需要排除不引入的类信息(EnableAutoConfiguration注解配置的exclude信息、excludeName信息、通过spring.autoconfigure.exclude配值得信息)。

6、校验配置得排除引入类信息,如果类存在但是并不是AutoConfiguration类则会抛出IllegalStateException异常信息。

7、从需要引入得AutoConfiguration类列表中删除需要排除引入得类。

8、从spring.factories中读取类型为AutoConfigurationImportFilter的Filter,实例化后对剩下的AutoConfiguration类进行过滤。

9、从spring.factories中读取类型为AutoConfigurationImportListener的Listener,实例化后向他们发布AutoConfigurationImportEvent事件。

10、构建AutoConfigurationEntry并返回。

AutoConfigurationImportSelector.process方法

1、判断当前传入的deferredImportSelector是否为AutoConfigurationImportSelector,否则抛出异常;

2、调用AutoConfigurationImportSelector.getAutoConfigurationEntry获取AutoConfigurationEntry实例;

3、将获得的AutoConfigurationEntry实例添加到AutoConfigurationGroup.autoConfigurationEntries中;

4、将AutoConfigurationEntry实例中的AutoConfiguration类名都添加到AutoConfigurationGroup.entries中;

 

DeferredImportSelectorGrouping.getImports方法:

1、调用AutoConfigurationGroup.process方法,对所有DeferredImportSelectorHolder实例进行处理

2、调用AutoConfigurationGroup.selectImports方法,整合AutoConfigurationGroup.autoConfigurationEntries中的所有AutoConfigurationEntry的AutoConfiguration类信息和排除引入的AutoConfiguration类信息,得到最终的需引入AutoConfiguration类信息,排序后包装成Entry对象返回;

DeferredImportSelectorGroupingHandler.processGroupImports方法:

1、调用所有DeferredImportSelectorGrouping的getImports方法获得需要引入得AutoConfiguration类信息;

2、将AutoConfiguration类信息转换为ConfigurationClass对象;

3、调用ConfigurationClassParser.processImports方法对ConfigurationClass进行解析

 

DeferredImportSelectorHandler.process方法

1、将所有DeferredImportSelectorHolder中的deferredImport实例注册到DeferredImportSelectorGroupingHandler实例中得groupings属性中。

2、调用DeferredImportSelectorGroupingHandler.processGroupImports方法

 

ConfigurationClassParser.parse方法

1、for循环读取传入得BeanDefinitionHolder,获取BeanDefinition信息,根据BeanDefinition类型得不同分别对信息进行处理生成ConfigurationClass对象,然后调用processConfigurationClass方法进行解析。

2、调用ConfigurationClassParser.deferredImportSelectorHandler.process()方法进行延迟引入信息得处理。

ConfigurationClassPostProcessor.processConfigBeanDefinitions方法

1、从BeanDefinitionRegistry实例中获取所有已经注册得BeanDefinition得名字

2、for循环中使用名字获取对应的BeanDefinition信息,根据BeanDefinition信息判断是否已经将该BeanDefinition对应的类作为ConfigurationClass处理,若不是则将根据BeanDefinition进行判断是否为ConfigurationClass,如果是则添加到待处理的ConfigurationClass列表中,也就是configCandidates中。

3、判断configCandidates为空则直接返回。

4、对configCandidates中的内容进行排序。

5、从BeanDefinitionRegistry中获取BeanNameGenerator实例,赋值给ConfigurationClassPostProcessor.componentScanBeanNameGenerator和ConfigurationClassPostProcessor.importBeanNameGenerator

6、检查environment是否为null,是则创建StandardEnvironment对象并赋值。

7、创建ConfigurationClassParser对象用于解析ConfigurationClass;

8、解析BeanDefinitionRegistry中已有的ConfigurationClass(调用ConfigurationClassParser.parse方法)

9、检验后面读取到的有Configuration注解的ConfigurationClass是否在没有配置proxyBeanMethods属性为false的同时设置了final 修饰符,如果是则抛出BeanDefinitionParsingException

10、使用ConfigurationClassBeanDefinitionReader加载ConfigurationClassParse解析到的所有configClasses,使用trackedConditionEvaluator进行条件化装配校验,如果不需要且BeanDefinitionRegistry已有对应的BeanDefinition,则从BeanDefinitionRegistry中将对应的BeanDefinition信息删除,如果需要加载则解析ConfigurationClass并生成BeanDefinition添加到BeanDefinitionRegistry中,解析ConfigurationClass中带Bean注解的方法,生成对应的BeanDefinition信息注册到BeanDefinitionRegistry中,解析ConfigurationClass中的ImportResource注解信息,从resource中读取信息,将需要加载的Bean信息注册到BeanDefinitionRegistry中,处理Import引入类型为ImportBeanDefinitionRegistrar时,生成对应的BeanDefinition注册到BeanDefinitionRegistry中。

11、对新增加的BeanDefinition信息进行解析,重复上述8~10步骤。

 

后续的调用方法栈:

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors
AbstractApplicationContext.invokeBeanFactoryPostProcessors
AbstractApplicationContext.refresh

属于ApplicationContext 刷新的基本步骤,刷新,调用BeanFactoryPostProcessor对BeanFactory进行处理,调用BeanDefinitionRegistryPostProcessor对BeanDefinitionRegistry进行处理,ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor,BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,所以会进行连续调用处理,

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry主要用于读取ConfigurationClass,也就是标有Configuration注解的类中的相关引入类的信息,如Import注解引入类,ImportResource注解引入类,Bean注解引入类的BeanDefinition加载注册。

 

总结:

在刷新ApplicationContext时,找到ApplicationContext和BeanFactory中的所有BeanFactoryPostProcessor实现类实例,先进行BeanDefinitionRegistryPostProcessor接口实现实例的postProcessBeanDefinitionRegistry方法调用,在while中处理,以免遗漏,然后进行BeanFactoryPostProcessor接口实现实例的postProcessBeanFactory方法调用。而这个过程中ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry会被调用用于处理BeanDefinitionRegistry中标有Configuration注解的BeanDefinition信息,此时有的是DemoApplication(SpringBootApplication注解继承SpringBootConfiguration注解和EnableAutoConfiguration注解,SpringBootConfiguration继承Configuration注解),在解析Configuration注解类时,会处理类上的Import注解,ImportResource注解和Bean注解引入的类信息还有PropertySources注解,PropertySource注解,ComponentScans注解,ComponentScan注解等相关处理。

当Import注解引入的类实现了DeferredImportSelector接口时,该对象会交给deferredImportSelectorHandler进行延迟引入处理;

如果实现了ImportSelector接口,该对象的selectImports方法会被调用,返回的类信息会作为ConfigurationClass继续处理;

如果实现了ImportBeanDefinitionRegistrar接口,则该对象会被添加到importBeanDefinitionRegistrars属性中等待后续处理,由ConfigurationClassBeanDefinitionReader进行引入BeanDefinition的处理。

而EnableAutoConfiguration上的Import注解引入了AutoConfigurationImportSelector,该类实现了DeferredImportSelector接口,经由deferredImportSelectorHandler进行分组化的处理后会加载读取spring.factories文件中EnableAutoConfiguration类型的自动装配类,将读取到需要引入的类信息解析创建BeanDefinition注册到BeanDefinitionRegistry,当使用到对应的bean时,BeanFactory会进行实例化。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值