Skip to content


源码跟踪技巧

在跟踪框架源码的时候,一定要抓住关键点,找到核心流程。一定不要从头到尾一行代码去看,一个方法的去研究,一定要找到关键流程,抓住关键点,先在宏观上对整个流程或者整个原理有一个认识,有精力再去研究其中的细节

源码分析

@SpringBootApplication

要搞清楚 SpringBoot 的自动配置原理,要从 SpringBoot 启动类上使用的核心注解@SpringBootApplication 开始分析:


在 @SpringBootApplication 注解中包含了

元注解

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

@SpringBootConfiguration


@SpringBootConfiguration 注解上使用了 @Configuration,表明 SpringBoot 启动类就是一个配置类

@Indexed 注解,是用来加速应用启动的(不用关心)

@ComponentScan


@ComponentScan 注解是用来进行组件扫描的,扫描启动类所在的包及其子包下所有被 @Component 及其衍生注解声明的类

SpringBoot 启动类,之所以具备扫描包功能,就是因为包含了 @ComponentScan 注解

@EnableAutoConfiguration


使用 @Import 注解,导入了实现 ImportSelector 接口的实现类

AutoConfigurationImportSelector 类是 ImportSelector 接口的实现类

AutoConfigurationImportSelector 类中重写了 ImportSelector 接口的 selectImports() 方法

selectImports() 方法底层调用 getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息集合

getAutoConfigurationEntry()方法通过调用 getCandidateConfigurations(annotationMetadata, attributes)方法获取在配置文件中配置的所有自动配置类的集合

getCandidateConfigurations 方法的功能:获取所有基于 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置类的集合

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件这两个文件在哪里呢?

通常在引入的起步依赖中,都有包含以上文件

在前面在给大家演示自动配置的时候,我们直接在测试类当中注入了一个叫 gson 的 bean 对象,进行 JSON 格式转换。虽然我们没有配置 bean 对象,但是我们是可以直接注入使用的。原因就是因为在自动配置类当中做了自动配置。到底是在哪个自动配置类当中做的自动配置呢?我们通过搜索来查询一下

在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件中指定了第三方依赖 Gson 的配置类:GsonAutoConfiguration

打开上面的第三方依赖中提供的 GsonAutoConfiguration 类

在 GsonAutoConfiguration 类上,添加了注解@AutoConfiguration,通过查看源码,可以明确:GsonAutoConfiguration 类是一个配置

看到这里,应该明白为什么可以完成自动配置了,原理就是在配置类中定义一个 @Bean 标识的方法,而 Spring 会自动调用配置类中使用 @Bean 标识的方法,并把方法的返回值注册到 IOC 容器中

自动配置源码小结

原理总结

自动配置原理源码入口就是 @SpringBootApplication 注解,在这个注解中封装了 3 个注解

(1)@SpringBootConfiguration

声明当前类是一个配置类

(2)@ComponentScan

进行组件扫描(SpringBoot 中默认扫描的是启动类所在的当前包及其子包)

(3)@EnableAutoConfiguration(自动配置的核心注解

封装了 @Import 注解(Import 注解中指定了一个 ImportSelector 接口的实现类)

在实现类重写的 selectImports()方法,读取当前项目下所有依赖 jar 包中 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 两个文件里面定义的配置类(配置类中定义了 @Bean 注解标识的方法)

当 SpringBoot 程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到 String 类型的数组中,最终通过 @Import 注解将这些配置类全部加载到 Spring 的 IOC 容器中,交给 IOC 容器管理

注意事项

在低版本(2.7.8 以前)的 Springboot 中,自动配置类(XxxxAutoConfiguration)是定义在 spring.factory 文件中

提出问题

在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定义的配置类非常多,而且每个配置类中又可以定义很多的 bean,那这些 bean 都会注册到 Spring 的 IOC 容器中吗?

答案:并不是。 在声明 bean 对象时,上面有加一个以 @Conditional 开头的注解,这种注解的作用就是按照条件进行装配,只有满足条件之后,才会将 bean 注册到 Spring 的 IOC 容器中

@Conditional 注解

基本介绍

(1)作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的 bean 对象到 Spring 的 IOC 容器中。

(2)位置:方法、类

(3)@Conditional 本身是一个父注解,派生出大量的子注解

@ConditionalOnClass:判断环境中有对应字节码文件,才注册 bean 到 IOC 容器

@ConditionalOnMissingBean:判断环境中没有对应的 bean(类型或名称),才注册 bean 到 IOC 容器

@ConditionalOnProperty:判断配置文件中有对应属性和值,才注册 bean 到 IOC 容器

@ConditionalOnClass

通过 name 属性指定类

java
@Configuration
public class HeaderConfig {

    @Bean
    @ConditionalOnClass(name="io.jsonwebtoken.Jwts")//环境中存在指定的这个类,才会将该bean加入IOC容器
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    //省略其他代码...
}

pom.xml

xml
<!--JWT令牌-->
<dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt</artifactId>
     <version>0.9.1</version>
</dependency>

测试类

java
@SpringBootTest
public class AutoConfigurationTests {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testHeaderParser(){
        System.out.println(applicationContext.getBean(HeaderParser.class));
    }

    //省略其他代码...
}

✅Tip

因为 io.jsonwebtoken.Jwts 字节码文件在启动 SpringBoot 程序时已存在,所以创建 HeaderParser 对象并注册到 IOC 容器中

@ConditionalOnMissingBean

java
@Configuration
public class HeaderConfig {

    @Bean
    @ConditionalOnMissingBean //不存在该类型的bean,才会将该bean加入IOC容器
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    //省略其他代码...
}

✅Tip

SpringBoot 在调用@Bean 标识的 headerParser()前,IOC 容器中是没有 HeaderParser 类型的 bean,所以 HeaderParser 对象正常创建,并注册到 IOC 容器中

@ConditionalOnProperty

(1)先在 application.yml 配置文件中添加如下的键值对

yml
name: itheima

(2)在声明 bean 的时候就可以指定一个条件 @ConditionalOnProperty

如果 name 属性 和 havingValue 属性与配置文件中的值不匹配,则在启动项目时会报错

java
@Configuration
public class HeaderConfig {

    @Bean
    @ConditionalOnProperty(name ="name",havingValue = "itheima")//配置文件中存在指定属性名与值,才会将bean加入IOC容器
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator(){
        return new HeaderGenerator();
    }
}

@EnableAutoConfiguration

(1)它封装了一个@Import 注解,Import 注解里面指定了一个 ImportSelector 接口的实现类

(2)在这个实现类中,重写了 ImportSelector 接口中的 selectImports()方法

(3)而 selectImports()方法中会去读取两份配置文件,并将配置文件中定义的配置类做为 selectImports()方法的返回值返回,返回值代表的就是需要将哪些类交给 Spring 的 IOC 容器进行管理

(4)那么所有自动配置类的中声明的 bean 都会加载到 Spring 的 IOC 容器中吗? 其实并不会,因为这些配置类中在声明 bean 时,通常都会添加@Conditional 开头的注解,这个注解就是进行条件装配。而 Spring 会根据 Conditional 注解有选择性的进行 bean 的创建

(5)@Enable 开头的注解底层,它就封装了一个注解 import 注解,它里面指定了一个类,是 ImportSelector 接口的实现类。在实现类当中,我们需要去实现 ImportSelector 接口当中的一个方法 selectImports 这个方法。这个方法的返回值代表的就是我需要将哪些类交给 spring 的 IOC 容器进行管理

(6)此时它会去读取两份配置文件,一份儿是 spring.factories,另外一份儿是 autoConfiguration.imports。而在 autoConfiguration.imports 这份儿文件当中,它就会去配置大量的自动配置的类

(7)而前面我们也提到过这些所有的自动配置类当中,所有的 bean 都会加载到 spring 的 IOC 容器当中吗?其实并不会,因为这些配置类当中,在声明 bean 的时候,通常会加上这么一类@Conditional 开头的注解。这个注解就是进行条件装配。所以 SpringBoot 非常的智能,它会根据 @Conditional 注解来进行条件装配。只有条件成立,它才会声明这个 bean,才会将这个 bean 交给 IOC 容器管理