1、SPI机制
1.1 SPI思想
SPI的全名为service provider interface,这个是针对厂商或者插件的,随意插拔。服务发现,就是不再导入特定的包路径,会在META-INF/下自己来配置接口信息,当需求场景有变化,只需要改变该配置信息,而不需要改变代码,Java spi就是提供这样一个机制:为某个接口寻找服务实现的机制
例如:jdbc路径cj问题,我们代码原来写死路径的话,没法改,变成配置文件告诉使用者,我提供接口是哪个类,这就动态了
1.2 SPI约定
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/service/目录下同时创建一个以服务接口名命的文件,这是自己定的。该文件里就是该服务具体实现类的全限定类名,就是对外开放的接口。而当外部程序装配这个模块的时候,就能通过jar包下的META-INF/service/里配置的文件找到具体的实现类名,并装载实例化,完成模块的注入。
1.3springBoot中的类SPI扩展机制
在springBoot的自动装配过程中
- SpringFactoriesLoader会加载META-INF/spring.factories文件
- 从classpath下每个jar包中搜索所有的META-INF/spring.factories配置文件
- 然后将解析properties文件,找到指定名称的配置后返回,工厂模式反射创建好Bean
- 需要注意的是,其实这里不仅仅是会去classPath路径下查找,回扫描所有路径下的jar包,只不过这个文件只会在jar包的ClassPath下,这是约定
分析:
虽然SpringFactoriesLoader会加载所有的META-INF/spring.factories文件,但是不同场景,或者说在不同方法的处理逻辑里,springBoot会对加载到的所有配置的类做一个过滤,比如在SpringAppliocation的构造方法中setInitializers()方法,会从所有spring.factories文件配置的类中过滤获取到ApplicationContextInitializer容器初始化相关的类名信息
setListeners()方法,则会过滤获取到所有配置的有关ApplicationListener容器监听相关的类的信息,
并先把它们保存到不同的集合中

1.4springBoot SPI机制,spring.Factories文件加载时机
可以看到springBoot一共加载了三个spring.factories文件,而且是在SpringApplication的构造方法中,第一次调用getSpringFactoriesInstances()就去加载了三个spring.factories文件,加载之后把文件中配置的类的全限定名信息保存到了SpringFactoriesLoader的cache中缓存了。Classload为key,MultiValueMap为值,以后其它地方需要会根据类加载器从cache缓存中获取到相应的配置类的信息,通过_getSpringFactoriesInstances()方法去获取,会传递一个字节码,进行过滤,只获取当前需要的spring.factories文件配置的类,然后通过createSpringFactoriesInstances()方法通过反射创建实例
_
2、 springBoot注解
2.1@SpringBootApplication注解
@SpringBootApplication注解是一个复合注解,除去四个元注解,分别是@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
2.1.1 @SpringBootConfiguration
@SpringBootConfiguration注解本质是一个@Configuration注解,而@Configuration注解本质是一个@Component注解

因此,加了@SpringBootConfiguration注解的启动类,本质就是一个配置类ConfigurationClass,此类中可以声明一个或者多个被@Bean注解标记的方法,spring可以将这类方法的返回值纳入IOC容器中,并且BeanName就是这个方法的名字。
2.1.1.1 @Configuration注解解析
1)在spring容器创建过程中,refresh()方法的执行,refresh()方法中有一个方法invokeBeanFactoryPostProcessors()这个方法执行spring内部,或者我们自己定义的BeanFactory后置处理器。@Configuration这个注解的解析就是通过BeanFactoryPostProcessor后置处理器来完成的,看代码可以知道,@Conmfiguration中也有ConfigurationClassPostProcessor这个后置处理器的提示信息
所以在容器创建过程中,执行invokeBeanFactoryPostProcessors()方法,ConfigurationClassPostProcessor会被执行
首先会执行postProcessBeanDefinitionRegistry()方法,调用了processConfigBeanDefinitions(registry)方法
2)processConfigBeanDefinitions()方法中通过ConfigurationClassParser的parse()方法进行了配置类的解析,
这个主要解析我们在使用@Configuration注解标注的类,接着又会调用parser.getConfigurationClasses()获取到全部的配置类,排除parse方法已经解析的,对剩余的配置类使用ConfigurationClassBeanDefinitionReader的loadBeanDefinitions(configClasses)进行Bean定义的解析,解析完成后,将解析到的所有Bean定义信息注册到BeanDefinitionRegistry中


3)被@Configuration注解标记的类标记为FULL配置类,被@Component、@ComponentSca、@Import、@ImportResources、@Bean等注解标记的类标记为LITE配置类
2.1.2 @ComponentScan
配置此注解可以执行包扫描,如果useDefaultFilters属性为true,那么它会将被@Component注解标记以及以@Component为元注解的注解标记的类都扫描到IOC容器中
@Controller、@Service、@Repository本质都是@Component注解
看源码上的注释:要么指定basePackageClasses,那么指定basePackages,或者只能怪其别名value值,都可以用来指定要扫描的包,如果没有指定,那么扫描将会从声明此注解的类开始,这也是springBoot启动类要放在根目录下的原因
2.1.3@EnableAutoConfiguration注解
spring中所有以@Enable开头的注解,一般都是通过@Import注解将特定的类导入到容器中去,而@Import注解可以导入的类型有三种
- 导入普通的类,beanName为全限定类名
- 导入ImportSelector接口实现
- 导入ImportBeanDefinitionRefistrar接口实现
@EnableAutoConfiguration注解导入的是ImportSelector接口实现AutoConfigurationImportSelector,所以导入容器的Bean中应该是接口方法返回的值,也就是所有要放入到容器的Bean的全类名字符串数组
注意:@Import的解析过程也是在执行这个ConfigurationClassPostProcessor后置处理器的时候完成解析的,解析的时候就会调用selectImports方法,而这个方法又是去加载spring.factories,这次是加载了所有,而SpringApplication构造方法中是获取部分

3、总结:
springBoot的自动配置,主要由SPI机制合BeanFactory后置处理器来完成的
