写在前面
SpringBoot毋庸置疑是目前流行度最高的框架。而这原因,即是大家最清楚的0配置化,不需要配置便可以启动一个tomcat服务。
我相信,只要你用过Spring Boot,就会对这样一个现象非常的好奇:
引入一个组件依赖,加个配置,这个组件就生效了。
为了故事的发展,我们举个例子来说,比如我们常用的Redis, 在Spring Boot中就是这样使用的
引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
配置yml文件
spring:redis:host:127.0.0.1port:6379password:123456
这就配置OK了,可以直接引用了,神奇嘛
@AutowiredprivateRedisTemplateredisTemplate;
就这样,两步搞定,中间没有做任何事情。这里面我们应该提出几个问题。
第一,pom文件redis是在哪里,怎么连版本号都没有,reids版本是什么
第二,RedisTemplate 是怎么交给Spring注入管理的,怎么就能用了呢。
不知道你们刚接触Boot的时候,有没有听说过约定大于配置
。我们要明白一个道理,事情总要有人做,自己不做,肯定是别人帮着做了,我们带着这些疑问去深入看源码。
SPI
先不着急,讲流程源码之前,我们先来学习下SPI,这个非常重要,Boot里面大都是靠这个机制在做事情。上一篇只是简单提了下,今天发现绕不开了。
SPI ,全称为 Service Provider Interface(服务提供者接口),是一种服务发现机制。它通过在classpath路径下的
META-INF/services
文件夹查找文件,自动加载文件中所定义的类。
为了故事的继续发展 我们建个工程
我们用医生给我们做核酸的场景:
spi-doctor 为服务提供方,可以理解为我们的框架
spi-person 为使用方,因为我的服务提供接口叫PlayHeSuan(做核酸),所以所有实现都是人~
我们看下代码
在spi-doctor模块中定义接口PlayHeSuan
/***服务提供者,做核酸*@Date2022/4/295:50下午*@Authorshushi*/publicinterfacePlayHeSuan{//做核酸voidplayHeSuan();}
在spi-person模块中定义两个实现类ZhangSan
,Lisi
/***李四做核酸*@Date2022/4/295:57下午*@Authorshushi*/publicclassLisiimplementsPlayHeSuan{@OverridepublicvoidplayHeSuan(){System.out.println("李四做核酸~~~~");}}
/***张三做核酸*@Date2022/4/295:56下午*@Authorshushi*/publicclassZhangSanimplementsPlayHeSuan{@OverridepublicvoidplayHeSuan(){System.out.println("张三做核酸~~~~");}}
编写配置文件
新建文件夹META-INF/services
在文件夹下新建文件cn.shushi.spi.doctor.PlayHeSuan
这里要特别说明 建配置文件一定要直接写 META-INF/services,不能直接写META-INF.services,否则会无效
文件里面写实现类的全路径
测试
publicclassSpiTest{publicstaticvoidmain(String[]args){ServiceLoader<PlayHeSuan>load=ServiceLoader.load(PlayHeSuan.class);load.forEach(PlayHeSuan::playHeSuan);}}
SPI就讲到这里,我们回过头来结合实例理解下。SPI的目的就是借助SPI理解自动装配
借助SPI理解自动装配
回顾一下我们做了什么,我们在resources下创建了一个文件,里面放了些实现类,然后通过ServiceLoader
这个类加载器就把它们加载出来了。
假设有人已经把编写配置之类的前置步骤完成了,那么我们是不是只需要使用下面的这部分代码,就能将PlayHeSuan
有关的所有实现类调度出来。
//使用Java的ServiceLoader进行加载ServiceLoader<PlayHeSuan>load=ServiceLoader.load(PlayHeSuan.class);load.forEach(PlayHeSuan::playHeSuan);
再进一步讲,如果再有人把上面这部分代码也给写了,然后把这些实现类全部注入到Spring容器里,那会发生什么?
相信到这里大家看到这里心里都已经有个谱了,我们接下来进入SpringBoot开始深入理解
SpringBoot的配置文件
在回想下SPI机制,他们都是通过在组件下放入一个配置文件完成的,那么Spring Boot是不是也这样的呢?。
是的 没错,我们可以通过pom文件的组名称来maven看到在这个下面,我们可以看到spring.factories
,翻译过来是spring工厂,是不是有那个感觉了,这个就是实例化的呗
很明显的看到最下面有个自动配置的注释,key还是个EnableAutoConfiguration,开启自动配置!
找到了!
我们接着找我们最开始说的redis
进入RedisAutoConfiguration类,看看里面是些什么代码
哈哈 破案了,终于破案了,这里不就是加载进去了嘛,配置文件我们也找到了:spring.factories,也实锤了就是通过这个配置文件进行的自动配置。
接下来,我们来尝试还原一下案情经过:
第一 通过某种方式读取spring.factories文件,紧接着把里面所有的自动配置类加载到Spring容器中
第二 然后就可以通过Spring的机制将配置类的@Bean注入到容器中了。
那接下来,我们就看是通过什么样的方式读取spring.factories,并且注入的,看到这里是不是感觉刚要进去的感觉,坚持看,相信都能看明白
如何注入
其实读取配置,就是注入,我们一起来看下看看Spring中有哪些注入方式
类似于@Component,@Bean这些,阿鉴就不说了,大家肯定见过一种这样的注解:EnableXxxxx
比如:EnableAsync
开启异步,大家好不好奇这样的注解是怎么生效的?一起来看下
大家有没有发现,框架中到处是Import注解,这里先不展开细讲,到时候讲Spring的时候,再细说,总之一句话,import就是将该类交给Spring管理,实例化该bean。
那回过头来进去 AsyncConfigurationSelector
,接着进去ProxyAsyncConfiguration
真相了,EnableAsync
就是通过这里(AsyncAnnotationBeanPostProcessor
)生效的
好的 我们接着研究最后一个注解,也是SpringBoot的启动核心注解
SpringBootApplication注解
我们在使用SpringBoot项目时,用到的唯一的注解就是@SpringBootApplication,所以我们唯一能下手的也只有它了,打开它看看吧。
进入AutoConfigurationImportSelector
核心逻辑selectImports
@OverridepublicString[]selectImports(AnnotationMetadataannotationMetadata){if(!isEnabled(annotationMetadata)){returnNO_IMPORTS;}AutoConfigurationMetadataautoConfigurationMetadata=AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AnnotationAttributesattributes=getAttributes(annotationMetadata);//获取候选的配置类,这个其实就是开始去拿配置类去了List<String>configurations=getCandidateConfigurations(annotationMetadata,attributes);//移除重复的配置configurations=removeDuplicates(configurations);//获取到要排除的配置Set<String>exclusions=getExclusions(annotationMetadata,attributes);checkExcludedClasses(configurations,exclusions);//移除所有要排除的配置configurations.removeAll(exclusions);//过滤掉不具备注入条件的配置类,通过Conditional注解configurations=filter(configurations,autoConfigurationMetadata);//通知自动配置相关的监听器fireAutoConfigurationImportEvents(configurations,exclusions);//返回所有自动配置类returnStringUtils.toStringArray(configurations);}
如何从配置文件读取的(getCandidateConfigurations)
protectedList<String>getCandidateConfigurations(AnnotationMetadatametadata,AnnotationAttributesattributes){//是不是有刚才那个SPI感觉了List<String>configurations=SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());//通过这个也能看出来是去读取META-INF/spring.factorieAssert.notEmpty(configurations,"NoautoconfigurationclassesfoundinMETA-INF/spring.factories.Ifyou"+"areusingacustompackaging,makesurethatfileiscorrect.");returnconfigurations;}
getSpringFactoriesLoaderFactoryClass
spring:redis:host:127.0.0.1port:6379password:1234560
结合上一步,就是加载配置文件,并且读取key为EnableAutoConfiguration的配置
接下来再看loadFactoryNames
spring:redis:host:127.0.0.1port:6379password:1234561
ok, 后面的过滤逻辑就不在这里说了,毕竟本节的重点是自动装配机制,小伙伴明白了原理就ok啦
好的 今天的自动装配原理就讲到这里,我们下期见
总结回顾
本篇介绍了关于SpringBoot的自动装配原理,我们先通过SPI机制进行了小小的热身,然后再根据SPI的机制进行推导Spring的自动装配原理,中间还带大家回顾了一下@Import注解的使用,最后成功破案~
最后给张图做下总结