Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之自动包规则原理
Posted 李阿昀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之自动包规则原理相关的知识,希望对你有一定的参考价值。
写在前面
在前面几篇文章中,我给大家介绍了一些Spring Boot的底层注解。接下来,我们就要深入地了解一下Spring Boot的自动配置原理了,我们来看一下Spring Boot到底是怎么神不知鬼不觉的帮我们做了那么多事情的。
有些同学可能会说啊,我他妈没事干嘛要知道这些什么屁底层原理啊,既然Spring Boot自动帮我们做了那么多事情,那我就只关心我业务逻辑开发就行了呗,还要学什么毛底层原理啊,他妈的,烦死了!你爽言爽语是爽完了,但那又有什么用呢?古人不是说过一句话吗,要知其然更要知其所以然嘛!所以,还是得深入地分析一下Spring Boot的自动配置原理哟!多学点东西,总不会错吧!你说是不是啊,我亲爱的读者们😁
接下来,我们就从咱们的主程序类开始分析起吧!主程序类里面有一个main方法,它是用来启动整个Spring Boot应用的。
package com.meimeixia.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
/**
* 主程序类,也叫主配置类
* @author liayun
* @create 2021-04-19 4:02
*/
// @SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.meimeixia.boot")
public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了当前应用的所有组件
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 这是固定写法哟
// 2. 我们可以来查看下IoC容器里面所有的组件,只要能查找到某一个组件,就说明这个组件是能工作的,至于怎么工作,这就是我们后来要阐述的原理了
String[] names = run.getBeanDefinitionNames(); // 获取所有组件定义的名字
for (String name : names) {
System.out.println(name);
}
// 3. 从容器中获取组件
// Pet tom01 = run.getBean("tom", Pet.class);
// Pet tom02 = run.getBean("tom", Pet.class);
// System.out.println("组件是否为单实例:" + (tom01 == tom02));
//
// // 配置类打印:com.meimeixia.boot.config.MyConfig$$EnhancerBySpringCGLIB$$4559f04d@49096b06
// MyConfig bean = run.getBean(MyConfig.class);
// System.out.println(bean);
//
// // 如果@Configuration(proxyBeanMethods = true),代理对象调用方法
// User user = bean.user01();
// User user1 = bean.user01();
// System.out.println("(user == user1) = " + (user == user1));
//
// User user01 = run.getBean("user01", User.class);
// Pet tom = run.getBean("tom", Pet.class);
// System.out.println("用户的宠物:" + (user01.getPet() == tom));
//
// // 4. 获取组件
// System.out.println("=====================");
// String[] beanNamesForType = run.getBeanNamesForType(User.class); // 获取我们给容器中注册的User类型的组件的名字
// for (String s : beanNamesForType) {
// System.out.println(s);
// }
//
// DBHelper bean1 = run.getBean(DBHelper.class);
// System.out.println(bean1);
// boolean tom_pet = run.containsBean("tom");
// System.out.println("容器中tom组件:" + tom_pet);
//
// boolean user01 = run.containsBean("user01");
// System.out.println("容器中user01组件:" + user01);
//
// boolean tom666 = run.containsBean("tom666");
// System.out.println("容器中tom666组件:" + tom666);
boolean haha = run.containsBean("haha");
boolean hehe = run.containsBean("hehe");
System.out.println("haha:" + haha);
System.out.println("hehe:" + hehe);
}
}
当然,最重要的还是@SpringBootApplication
这个核心注解,我之前也说过了,该注解其实就相当于@SpringBootConfiguration
、@EnableAutoConfiguration
以及@ComponentScan("com.meimeixia.boot")
这三个注解。所以,在这里呢,我就将@SpringBootApplication
拆分成三个注解来看,来挨个分析该注解里面的功能,即分析一下该注解给都我们做了些什么。
自动包规则原理
我们不妨点进@SpringBootApplication
注解的源码里面去看一看,如下图所示。
可以清楚地看到等价于它的组合注解里面第一个就是@SpringBootConfiguration
。接下来,我们就要挨个分析这些组合注解了。
@SpringBootApplication给我们做了些什么呢?
首先,我们分析的第一个注解必然是其中的@SpringBootConfiguration
,该注解到底有什么厉害之处呢?下面我们就来仔细看看。
@SpringBootConfiguration的功能
我们不妨点进@SpringBootConfiguration
注解的源码里面去看一看,如下图所示,那这个注解到底是来干嘛的呢?
发现该注解上面又标注了一个@Configuration
注解,说明当前注解就是一个配置类。也就是说,咱们当前的主程序类其实就是Spring Boot里面的一个配置类,而且它还是一个核心配置类哟!
至于该注解还有其他要说明的东东吗?我想是没有必要了,没有必要再说了。OK,下面,我们来接着说说组合注解里面的第二个注解,即@ComponentScan
。
@ComponentScan的功能
该注解我之前就已详细讲解过了,如果还不了解该注解,那么不妨看看我写的以下这两篇文章,相信你看了之后,一定会对它有一个比较深入的认识。
其实,说到底,该注解无非就是用来给我们指定要扫描哪些东东的,在此我就不再赘述了,因为再讲一遍该注解,目前意义已经不是很大了。
接下来,我们就要说说组合注解里面的最后一个注解了,即@EnableAutoConfiguration
,该注解非常重要,因为所有的重量全部都压在它身上了。
@EnableAutoConfiguration的功能
@EnableAutoConfiguration
注解都干了些什么呢?带着这个疑问不妨点进@EnableAutoConfiguration
注解的源码里面去看一看,如下图所示。
发现它也是一个合成注解,除了那些元注解信息外,还有其他的两个注解,即@AutoConfigurationPackage
和@Import({AutoConfigurationImportSelector.class})
。也就是说,@EnableAutoConfiguration
注解就是这俩注解的合成。那问题来了,这俩注解分别都干了些啥呢?不急,下面我一个一个来仔细讲解一番。
首先,来讲一下@AutoConfigurationPackage
注解,我们来看一下这个注解帮我们都干了些什么活。只从字面意思上看的话,该注解翻译过来就是自动配置包的意思,那这又是啥意思呢?我们不妨点进该注解的源码里面去看一看,如下图所示。
发现在该注解上面又标注了一个@Import({Registrar.class})
注解,我们之前讲过这个注解,它就是用于给容器中导入一个组件的。那导入的是一个什么组件呢?很显然,导入的是一个Registrar类型的组件。你可能就会问了,既然导入了一个Registrar类型的组件,那么它的作用又是什么呢?我们不妨点进Registrar类里面去探个究竟,如下图所示,发现它是AutoConfigurationPackages抽象类里面的一个内部类,而且它里面还有两个方法。
相信大家不难猜到这点,就是@AutoConfigurationPackage
注解是利用Registrar类型的组件来给容器里面批量注册组件的。因为使用@Import
注解一个一个导那未免太麻烦了,所以就需要写一段代码来批量注册组件了。不知你看到没有,@AutoConfigurationPackage
注解是调用Registrar类型组件的registerBeanDefinitions方法来批量注册组件的哟!
所以,我们现在知道的第一个事情,就是@AutoConfigurationPackage
注解是利用Registrar类型组件给容器中导入组件的,而且导入的还不是一个组件,而是一系列组件,哪一系列组件呢?我们不妨在registerBeanDefinitions方法里面打一个断点,如下图所示,然后以Debug模式启动咱们的Spring Boot应用来看看。
这时,程序来到了registerBeanDefinitions方法里面,如下图所示,可以看到该方法传入了两个参数,一个AnnotationMetadata类型的metadata参数,它代表的就是@AutoConfigurationPackage
注解的元信息,注解的元信息代表的就是该注解能标在哪。要知道这一点,也很简单,查看一下metadata参数的值就能知道了。
怎么查看metadata参数的值呢?一共有两种方式,我分别来为大家详细介绍一下,大伙也不要嫌我烦,因为毕竟有些小伙伴对IDEA这个开发工具的Debug调试会感到些许陌生,讲透一点会显得比较友好😊
第一种方式,将光标放在你要查看的东东上,例如metadata参数,这时应该会弹出一个类似如下的小弹框。
然后点击该小弹框就能查看到你要查看的东东了,例如这儿查看metadata参数的结果如下图所示。
如果你还想查看得更清楚,那么依次展开其里面的每一项即可。
第二种方式,直接在IDEA控制台中查看你要查看的变量即可,如下图所示,这种方式可够简单吧!
以上这两种方式,你觉得哪一种更好呢?一句话,爱用哪种用哪种吧!
回到主题,大家猜一下,你觉得@AutoConfigurationPackage
注解应该是标注在哪儿啊?相信大伙不难猜到它是标注在咱们主程序类(例如MainApplication)里面的。因为主程序类上的@EnableAutoConfiguration
是一个合成注解,而它里面又包含了@AutoConfigurationPackage
注解,所以很显然该注解是标注在主程序类里面的。而且,我们也能从注解的元信息(即metadata参数的值)里面看到,@AutoConfigurationPackage
注解是标在com.meimeixia.boot.MainApplication
这个类(即主程序类)上的。
我们也知道现在程序是停留在了registerBeanDefinitions方法里面,那么它在这又干了一件什么事呢?拿到注解元信息之后,new了一个AutoConfigurationPackages.PackageImports
对象,然后再通过调用该对象的getPackageNames方法获取到一个包名。那获取的包名是什么呢?检查一下(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()
表达式的值便知,如下图所示。
发现获取到的包名是com.meimeixia.boot
。为什么会这样呢?这是因为@AutoConfigurationPackage
注解是标注在主程序类上的,而主程序类所在的包就是这个包,也就是说,相当于是获取到了主程序类所在的包。接着,大家也能看到,会将该包名封装到一个String数组里面,并注册进去,不难想像,此时@AutoConfigurationPackage
注解就是利用Registrar类型的组件来把某一个包下的所有组件批量注册进来的。
哦豁,原来是这样啊!现在我们终于给搞清楚了,@AutoConfigurationPackage
注解就是用于导入指定包下的所有组件。哪个包呢?想必大家已经很清楚了,就是主程序类所在的包。这也就解释了一个事情,解释了什么事情呢?就是为什么默认扫描的包路径是主程序类所在的包。原因就是在这,看来每一个默认规则其实在底层都有其源码的体现。
至此,我就为大家讲清楚了@AutoConfigurationPackage
注解。不是还有@Import(AutoConfigurationImportSelector.class)
这样一个注解要讲吗?怎么不接着讲了啊!这是因为篇幅所限,怕文章写的太长,大伙看起来很疲惫,所以就留在下一篇文章来讲了,敬请期待哟😜
以上是关于Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之自动包规则原理的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之HttpEncodingAutoConfiguration自动配置类
Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之初始加载自动配置类
Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之CacheAutoConfiguration自动配置类
Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之CacheAutoConfiguration自动配置类