虽然是3,但是原理是一样样的
首先聊一下,为什么要去学这个点东西,当然是面试比较常问啊hhh(谁让咱家SB比较火热呢),为什么 比较常问呢?(因为大家都爱问,我不问岂不是显得我很特别?)因为日常的生产中啊,咱们经常会定义一些公共的组件,提供给各个团队的使用,为了将别人使用这些组件的时候更方便,我们通常会把这些组件定义成一些公共的starter,比如(mybatis-spring-boot-starter),那如果说你想自定义starter的话,你就必须先了解自动配置的原理(自定义starter教程明天发,大家可以先去看看mybatis的starter,绝不白雪)等你看完这篇教程以后呢,就可以完美的解决以上的问题,到时候年薪百万不是梦啊(我不行但你们肯定行hang~)
正文
所谓的自动配置,就是boot工程启动后,起步依赖中的一些bean对象会自动的注入到ioc容器里面去(约定大于配置)
之前我们学习注册三方bean的时候,流程是
【引入三方jar——编写配置类——使用@Bean|@Import将其进行导入,(不理解看我上一篇)】
我们日常编写的公共组件就可以看做是第三方的jar,我们想要使用这些组件带来的便捷,说白了就是拿着里面的类直接用,就需要把他放到IOC容器里面交给Spring管理,上述的流程虽然能够完成注册Bean到IOC,但是,==自动了么?==回顾一下,我们去引入mybatis的起步依赖之后,像SqlSessionFactoryBean这种Bean对象就自动的注入到IOC容器里面去了,我们有去做相应的配置么?答案显然是否定的。那究竟怎么才能让它自动呢?接下来我们从源码里去找一下答案,看看SpringBooot的自动配置究竟是怎么个事。
前戏引入
都说天下文章一大抄,你也抄来我也抄。在学习自动配置之前,我们先来验证一个问题,引入了对应的起步依赖,启动后会不会自动的把对应的bean对象注入到ioc容器里
就以 spring-boot-starter-web起步依赖为案例,【验证:工程启动后会不会向ioc容器里面注入DispatcherServlet】
依次展示:pom文件、yml文件以及工程里包结构,可以看到除了SpringBoot的核心依赖以外,该项目没有做任何其他配置
@SpringBootApplication
public class Sb3AutoConfigApplication {
public static void main(String[] args) {
//获取容器
ApplicationContext context = SpringApplication.run(Sb3AutoConfigApplication.class, args);
//尝试获取DispatcherServlet对象并打印到控制台
System.out.println("打印结果:"+context.getBean("dispatcherServlet"));
}
}
结果显而易见没有!
刷新maven之后,再次启动工程,结果如下所示:
结果显而易见,引入web的起动依赖之后,确实把DispatcherServlet这个Bean给【自动】注册进来了。(哇哦,好神奇,怎么做到的呢?)接下来,我们就要翻看源码了。
通常情况下,要实现自动装配,只需要在启动类上加上@SpringBootApplication注解就可以实现了,今天我们就从这个注解开始看起,依旧是以DispatcherServlet为案例。
可以看到这是一个组合注解,主要组合了框起来的三个注解,其中@ComponentScan,就是一个包扫描的注解;点击@SpringBootConfiguration,进入内部,见下图,发现这也是一个复合注解,组合了@Configuation这个注解,变相的说明了咱们的启动类其实也是一个配置类;接着最后一个@EnableAutoConfiguration,见下图【这是自动配置的核心注解】发现里面有一个我们非常熟悉的注解@Import,里面的参数@Import(AutoConfigurationImportSelector.class),是不是更加熟悉了(不熟悉的看上一篇找@Import注解),接下类我们就来看看这个
可以看到它实现了DeferredImportSelector这个接口,而DeferredImportSelector这个接口又继承自@ImportSelector接口,所以等于AutoConfigurationImportSelector间接实现了ImportSelector接口
既然实现了Import接口,那必然实现了里面的selectImports方法,我们上一篇讲过,selectImports这个方法,将来会被我们的SpringBoot自动调用,从而得到它返回的全类名的字符串数组,然后把对应的bean对象注入到IOC容器里面去。【tips:这里我们进行代码剖析采用的是DeferredImportSelector的实现类,而不是ImportSelector,因为原来的ImportSelector这一套流程太过繁琐,不易理解,虽然Spring对于DeferredImportSelector有一套另外的处理流程,但是这并不影响我们对整个自动配置的原理的研究】
我们在研究这个selectImports方法的时候,说过这个返回的全类名数组,并不是写死的,而是写到了一个配置文件里面,然后进行读取,再返回到这个数组里面,而SpringBoot一样需要去读取一个配置文件来获取这些全类名,再返回回去。接下来我们重点来看看,这个全类名的配置文件在哪里!
可以看到上述方法的最终是把括号里面的参数【autoConfigurationEntry.getConfigurations()】这个的结果进行转成了数组进行返回,也就是说最终的结果是跟autoConfigurationEntry(红色框)这个对象有关的,那么这个对象又是上一步里面的(黄色框)getAutoConfigurationEntry(annotationMetadata)有关,所以这个配置文件究竟在哪里,getAutoConfigurationEntry这个方法应该就能告诉我们答案了,点击进入:
可以看到这个方法最终的返回值是有两个参数的,很明显 configurations是跟我们配置相关的参数,而这个参数的来源是上述的【getCandidateConfigurations(annotationMetadata, attributes)】这个方法,我们继续追踪
接下来我们看到这个configurations的产生是加载了一个东西诞生的,上边方法的跟踪比较繁琐且不易理解,我们直接看下边里面有一个断言,说这个configurations不能为空,如果为空的时候,就追加上这么一个提示(绿框),意思是没有自动配置的类在下边的路径【META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports后续简称为import文件】里面找到。
也就是说明了正常应该去这个配置文件里面去找对应的陪配置类的,那么这个import配置文件在哪里呢?
还记不记得我们在写SPringBoot项目的时候都会引入一个核心依赖,接下来我们进入这里
会发现里面有一个依赖叫做【spring-boot-autoconfigure】,很明显这个就是跟自动配置有关的
接下来在我们的依赖里去找这个玩意
A:看看我发现了什么?跟上面import文件一样的路径,哇哦,文件名也一样呢!猜猜里面有什么,我先来,不许跟我一样(要注册bean的全类名)
B:呵呵,你(我)真棒
A:点击进去看看,呵,果然是的。
B:ei我们要干嘛来着?
A:看看引入web依赖后是不是会有DispatcherServlet啊。
B:废话,这个我们不是已经验证过了么!
A:哦,是要看DispatcherServlet这个Bean是怎么被自动注册的!
B:白眼(还真是的)
【tips:在网上看其他的有关自动配置的时候,大多这个文件都是spring.factories 为啥?SB2.7以前是这个,2.7-3.0变成了二者都有,3.0以后就全是imports了】
接下来我们就看看这个跟DispatcherServlet有关的自动配置类在哪里!
Ctrl + F开始搜索:
找到了,进去看看
分析一下@AutoConfigureOrder,控制加载顺序的,白雪;@AutoConfiguration,自动配置的,说明这个也是一个配置类(白说明,说的我不知道一样);@ConditionalOnWebApplication看当前应用是不是web决定是否配置的,白雪,最后一个【@ConditionalOnClass(DispatcherServlet.class)】条件注解,意思就是DispatcherServlet这个类存在就配置生效,就自动注入一个DispatcherServlet的bean对象,不存在就不生效,也就不注入了
再往下看,这里面还有一个类,答案就显明易见了,这就是DispatcherServlet这个Bean被注册的核心了,仔细看看是不是也就那样吧
跟我们自己写的@Bean区别在那里呢?这里的这个(内部)类被写到了一个配置类里面,这个配置类放在了一个配置文件(imports),这个配置文件Spring会自动解析,解析到了后发现里面还有一个内部类(内部类上面有@Configuration说明还是配置类),就会继续解析,一直到把这个bean对象解析到IOC容器里面去。到这里,源码的解析就算完成了,接下来我们来做一个总结。
1、我们是从@SpringBootApplication这个注解入手的,这是一个复合注解,里面很重要的一个注解是开启自动配置@EnableAutoConfiguration注解,
2、这个注解呢也是一个复合注解,里面复合了@Import注解,这里呢导入了一个AutoConfigurationImportSelector这么个类,
3、这个类是ImportSelector的实现类,实现了一个selectImports方法
4、这个方法的内部通过层层的调用,最终会读取到一个配置文件,也就是【META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.】
5、这个配置文件里面有一堆的自动配置类的全类名,Spring启动的时候会自动的加载这些(通过SpringFactoriesLoader)其中有一个类叫做【DispatcherServletAutoConfiguration】这就是用来完成DispatcherServlet这个类的自动注入的
6、这个配置类呢上面有一个注解叫做【@AutoConfiguration】,说明当前类是一个自动配置类,第二个注解【@ConditionalOnClass(DispatcherServlet.class)】用来设置该对应bean的注册条件,即当前环境里面有DIspatcherServlet这个类呢,这个配置就生效,就会自动注入,(那么当前环境怎么才会有这个DIspatcherServlet这个类呢?是不是我引入了web的起步依赖就有了,那么没有这个类说明什么?没有引入web起步依赖)所以我们才说,当你引入了web起步依赖的时候,SpringBoot就会自动的帮你注入DispatcherServlet这个bean
7、自动注入的核心,就在最后的返回值为DispatcherServlet的方法,并在上面加上@Bean注解,最终把方法的返回值自动的注入到IOC容器里面
8、核心在哪里?那个imports文件里,再次强调SB2.7以前是spring.factories,2.7-3.0变成了二者都有,3.0以后就全是*.imports了
看了这么久,其实这个自动配置无非就是提供一个自动配置类,把这个配置类,写到指定的(*.imports)文件里,就可以了。后续等到SpringBoot启动后(加载到这个指定的配置类即可,怎么知道配置类的位置?(约定大于配置的思想啦)拿到了这些第三方的jar里面的配置类,再通过@ImportSelector接口实现对这些配置类的动态加载!
以上就是个人对于Spring自动配置的所有理解啦,篇幅有点长,希望能对你的成长有所帮助。
ringBoot启动后(加载到这个指定的配置类即可,怎么知道配置类的位置?(约定大于配置的思想啦)拿到了这些第三方的jar里面的配置类,再通过@ImportSelector接口实现对这些配置类的动态加载!
以上就是个人对于Spring自动配置的所有理解啦,篇幅有点长,希望能对你的成长有所帮助。