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自动配置类

Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之AopAutoConfiguration自动配置类

Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之AopAutoConfiguration自动配置类