SpringBoot自动配置源码走读

      Spring所拥有的强大功能之一就是可以集成各种开源软件。但随着互联网的高速发展,各种框架层出不穷,这就对系统架构的灵活性、扩展性、可伸缩性、高可用性都提出了新的要求。随着项目的发展,Spring慢慢地集成了更多的开源软件,引入大量配置文件,这会导致程序出错率高、运行效率低下的问题。为了解决这些状况,Spring Boot应运而生。但Spring Boot本身并不提供Spring的核心功能,而是作为Spring的脚手架框架,以达到快速构建项目、预置三方配置、开箱即用的目的。

1、Spring Boot项目结构

1.1、Spring Boot整体项目结构(以SpringBoot2.2.2版本为例)

地址:https://github.com/spring-projects/spring-boot/releases/tag/v2.2.2.RELEASE
在这里插入图片描述

      不同版本之间的Spring Boot源代码的顶层目录结构会有所变化,但并不影响其核心功能。v2.2.2.RELEASE版本由以下子模块构成。

  • spring-boot-project:Spring Boot核心项目代码,包含核心、工具、安全、文档、starters等项目。
  • spring-boot-tests:Spring Boot部署及集成的测试。
    关于顶层目录结构,我们有个简单了解即可。

1.2、spring-boot-project项目结构

      该模块包含了Spring Boot所有的核心功能。

  • spring-boot:Spring Boot核心代码,也是入口类SpringApplication类所在项目。
  • spring-boot-actuator:提供应用程序的监控、统计、管理及自定义等相关功能。
  • spring-boot-actuator-autoconfigure:针对actuator提供的自动配置功能。
  • spring-boot-autoconfigure:Spring Boot自动配置核心功能,默认集成了多种常见框架的自动配置类等。
  • spring-boot-cli:命令工具,提供快速搭建项目原型、启动服务、执行Groovy脚本等功能。
  • ·spring-boot-dependencies:依赖和插件的版本信息。
  • spring-boot-devtools:开发者工具,提供热部署、实时加载、禁用缓存等提升开发效率的功能。
  • spring-boot-docs:参考文档相关内容。
  • spring-boot-parent:spring-boot-dependencies的子模块,是其他项目的父模块。
  • spring-boot-properties-migrator:Spring Boot 2.0版本新增的模块,支持升级版本配置属性的迁移。
  • spring-boot-starters:Spring Boot以预定义的方式集成了其他应用的starter集合。
  • spring-boot-test:测试功能相关代码。
  • spring-boot-test-autoconfigure:测试功能自动配置相关代码。
  • spring-boot-tools:Spring Boot工具支持模块,包含Ant、Maven、Gradle等构建工具。

1.3、Spring Boot的整体架构

       下图为了更清晰地表达Spring Boot各项目之间的关系,我们基于依赖的传递性,省略了部分依赖关系。比如,Spring Boot Starters不仅依赖了Spring Boot Autoconfigure项目,还依赖了Spring Boot和Spring,而Spring Boot Autoconfigure项目又依赖了Spring Boot,Spring Boot又依赖了Spring相关项目。因此在图中就省略了Spring Boot Starters和底层依赖的关联。
在这里插入图片描述
       pring Boot Parent是Spring Boot及图中依赖Spring Boot项目的Parent项目,同样为了结构清晰,图中不显示相关关联。
      从上图中我们可以清晰地看到Spring Boot几乎完全基于Spring,同时提供了Spring Boot和Spring Boot Autoconfigure两个核心的模块,而其他相关功能又都是基于这两个核心模块展开的。

2、Spring Boot核心运行原理

      Spring Boot最核心的功能就是自动配置,功能的实现都是基于“约定优于配置”的原则。那么Spring Boot是如何约定,又是如何实现自动配置功能的呢?

2.1、核心运行原理

      使用Spring Boot时,我们只需引入对应的Starters,Spring Boot启动时便会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是Spring Boot的自动配置功能。我们先从整体上看一下Spring Boot实现该运作机制涉及的核心部分,如下图所示。

      上图描述了Spring Boot自动配置功能运作过程中涉及的几个核心功能及其相互之间的关系包括@EnableAutoConfiguration、spring.factories、各组件对应的AutoConfiguration类、@Conditional注解以及各种Starters。
      可以用一句话来描述整个过程:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。
概念:

  • @EnableAutoConfiguration:该注解由组合注解@SpringBootApplication引入,完成自动配置开启,扫描各个jar包下的spring.factories文件,并加载文件中注册的AutoConfiguration类等。
  • spring.factories:配置文件,位于jar包的META-INF目录下,按照指定格式注册了自动配置的AutoConfiguration类。spring.factories也可以包含其他类型待注册的类。该配置文件不仅存在于Spring Boot项目中,也可以存在于自定义的自动配置(或Starter)项目中。
  • AutoConfiguration类:自动配置类,代表了Spring Boot中一类以XXAutoConfiguration命名的自动配置类。其中定义了三方组件集成Spring所需初始化的Bean和条件。
  • @Conditional:条件注解及其衍生注解,在AutoConfiguration类上使用,当满足该条件注解时才会实例化AutoConfiguration类。
  • Starters:三方组件的依赖及配置,Spring Boot已经预置的组件。Spring Boot默认的Starters项目往往只包含了一个pom依赖的项目。如果是自定义的starter,该项目还需包含spring.factories文件、AutoConfiguration类和其他配置类。

3、运作原理源码解析

3.1、入口类和@SpringBootApplication注解

      Spring Boot项目创建完成会默认生成一个*Application的入口类。在默认情况下,无论是通过IDEA还是通过官方创建基于Maven的Spring Boot项目,入口类的命名规则都是artifactId+Application。通过该类的main方法即可启动Spring Boot项目,代码如下:

@SpringBootApplication
public class SpringBootLearnApplication {
   
    public static void main(String[] args) {
   
      SpringApplication.run(SpringBootLearnApplication.class, args);
    }
}

      这里的main方法并无特别之处,就是一个标准的Java应用的main方法,用于启动Spring Boot项目的入口。在默认情况下,把按照上述规则命名并包含main方法的类称为入口类。
      在Spring Boot入口类(除单元测试外)中,唯一的一个注解就是@SpringBootApplication。它是Spring Boot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableAutoConfiguration开启了自动配置,@SpringBootApplication部分源代码如下:

package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@Target({
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {
   @Filter(
    type = FilterType.CUSTOM,
    classes = {
   TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
   AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
   
    //排除指定自动配置类
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {
   };
    //排除指定自动配置类名
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {
   };
    // 指定扫描的基础包,激活注解组件的初始化
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {
   };
    //指定扫描的类,用于初始化
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {
   };
    //指定是否代理@Bean方法以强制执行bean的生命周期行为
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

      通过源代码可以看出,该注解提供了以下成员属性(注解中的成员变量以方法的形式体现)。

  • exclude:根据类(Class)排除指定的自动配置,该成员属性覆盖了@SpringBootApplication中组合的@EnableAutoConfiguration中定义的exclude成员属性。
  • excludeName:根据类名排除指定的自动配置,覆盖了@EnableAutoConfiguration中的excludeName的成员属性。
  • scanBasePackages:指定扫描的基础package,用于激活@Component等注解类的初始化。
  • scanBasePackageClasses:扫描指定的类,用于组件的初始化。
  • proxyBeanMethods:指定是否代理@Bean方法以强制执行bean的生命周期行为。此功能需要通过运行时生成CGLIB子类来实现方法拦截。该子类有一定的限制,比如配置类及其方法不允许声明为final等。proxyBeanMethods的默认值为true,允许配置类中进行inter-bean references(bean之间的引用)以及对该配置的@Bean方法的外部调用。如果@Bean方法都是自包含的,并且仅提供了容器使用的普通工程方法的功能,则可设置为false,避免处理CGLIB子类。一般情况下都配置为false。
          通过以上源代码我们会发现,Spring Boot中大量使用了@AliasFor注解,该注解用于桥接到其他注解,该注解的属性中指定了所桥接的注解类。如果点进去查看,会发现@SpringBootApplication定义的属性在其他注解中已经定义过了。之所以使用@AliasFor注解并重新在@SpringBootApplication中定义,更多是为了减少用户使用多注解带来的麻烦。@SpringBootApplication注解中组合了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan。因此,在实践过程中也可以使用这3个注解来替代@SpringBootApplication。在Spring Boot早期版本中并没有@SpringBootConfiguration注解,版本升级后新增了@SpringBootConfiguration并在其内组合了@Configuration。@EnableAutoConfiguration注解组合了@AutoConfigurationPackage。
    我们忽略掉一些基础注解和元注解,@SpringBootApplication注解的组合结构见下图:
    在这里插入图片描述
          @SpringBootApplication除了组合元注解之外,其核心作用还包括:激活Spring Boot自动配置的@EnableAutoConfiguration、激活@Component扫描的@ComponentScan、激活配置类的@Configuration。

3.2、@ComponentScan注解

      @ComponentScan主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中。@Controller,@Service,@Repository注解,查看其源码你会发现,他们中有一个共同的注解@Component。@ComponentScan注解默认就会装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。

3.3、@Configuration注解

       用@Configuration注释类表明其主要目的是作为bean定义的源
@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean之间的依赖关系。

3.4、@EnableAutoConfiguration注解

       @EnableAutoConfiguration是开启自动配置的注解,在创建的Spring Boot项目中并不能直接看到此注解,它是由组合注解@SpringBootApplication引入的。
      在未使用Spring Boot的情况下,Bean的生命周期由Spring来管理,然而Spring无法自动配置@Configuration注解的类。而Spring Boot的核心功能之一就是根据约定自动管理该注解标注的类。用来实现该功能的组件之一便是@EnableAutoConfiguration注解。@EnableAutoConfiguration位于spring-boot-autoconfigure包内,当使用@SpringBootApplication注解时,@EnableAutoConfiguration注解会自动生效。
      @EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常是基于项目classpath中引入的类和已定义的Bean来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的jar包中。
      举个例子:如果将tomcat-embedded.jar添加到classpath下,那么@EnableAutoConfiguration会认为你准备使用TomcatServletWebServerFactory类,并帮你初始化相关配置。与此同时,如果自定义了基于ServletWebServerFactory的Bean,那么@EnableAutoConfiguration将不会进行TomcatServletWebServerFactory类的初始化。这一系列的操作判断都由Spring Boot来完成。
      @EnableAutoConfiguration注解的源码如下:

package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({
   AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
   
    //用来覆盖配置开启/关闭自动配置的功能
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    //根据类(Class)排除指定的自动配置
    Class<?>[] exclude() default {
   };
    //根据类名排除指定的自动配置
    String[] excludeName() default {
   };
}

      @EnableAutoConfiguration注解提供了一个常量和两个成员参数的定义:

  • ENABLED_OVERRIDE_PROPERTY:用来覆盖开启/关闭自动配置的功能。
  • exclude:根据类(Class)排除指定的自动配置。
  • excludeName:根据类名排除指定的自动配置。
          正如上文所说,@EnableAutoConfiguration会猜测你需要使用的Bean,但如果在实战中你并不需要它预置初始化的Bean,可通过该注解的exclude或excludeName参数进行有针对性的排除。比如,当不需要数据库的自动配置时,可通过以下两种方式让其自动配置失效:
//通过@SpringBootApplication排除DataSourceAutoConfiguration
@SpringBootApplication(exclude=DataSourceAutoConfiguration.class)
public class SpringBootLearnApplication {
   
    public static void main(String[] args) {
   
      SpringApplication.run(SpringBootLearnApplication.class, args);
    }
}

//通过@SpringBootApplication排除DataSourceAutoConfiguration
@SpringBootApplication(excludeName=DataSourceAutoConfiguration.class)
public class SpringBootLearnApplication {
   
    public static void main(String[] args) {
   
      SpringApplication.run(SpringBootLearnApplication.class, args);
    }
}

      需要注意的是,被@EnableAutoConfiguration注解的类所在package还具有特定的意义,通常会被作为扫描注解@Entity的根路径。这也是在使用@SpringBootApplication注解时需要将被注解的类放在顶级package下的原因,如果放在较低层级,它所在package的同级或上级中的类就无法被扫描到。

3.5、@AutoConfigurationImportSelector注解

      @EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源代码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。
      @Import(AutoConfigurationImportSelector.class)又可以分为两部分:@Import和对应的ImportSelector。下面讲解@Import的基本使用方法和ImportSelector的实现类AutoConfigurationImportSelector。

3.5.1、@Import注解

      @Import注解位于spring-context项目内,主要提供导入配置类的功能。为什么要专门讲解@Import的功能及使用呢?如果查看Spring Boot的源代码,我们会发现大量的EnableXXX类都使用了该注解。了解@Import注解的基本使用方法,能够帮助我们更好地进行源代码的阅读和理解。@Import的源码如下:

package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
   
    Class<?>[] value();
}

      @Import的作用和xml配置中标签的作用一样,我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO(将其注册成Spring Bean,导入POJO需要Spring 4.2以上版本)。

3.5.2、@ImportSelector接口

      @Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。ImportSelector接口源码如下:

package org.springframework.context.annotation;
import org.springframework.core.type.AnnotationMetadata;
public interface ImportSelector {
   
    String[] selectImports(AnnotationMetadata var1);
}

      ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。
      如果实现了接口ImportSelector的类的同时又实现了以下4个Aware接口,那么Spring保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为:EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware和ResourceLoaderAware。
     在AutoConfigurationImportSelector的源代码中就实现了这4个接口,部分源代码及Aware的全限定名代码如下:

package org.springframework.boot.autoconfigure;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
   
...
}

     在上面的源代码中,AutoConfigurationImportSelector并没有直接实现ImportSelector接口,而是实现了它的子接口DeferredImportSelector。DeferredImportSelector接口与ImportSelector的区别是,前者会在所有的@Configuration类加载完成之后再加载返回的配置类,而ImportSelector是在加载完@Configuration类之前先去加载返回的配置类。
     DeferredImportSelector的加载顺序可以通过@Order注解或实现Ordered接口来指定。同时,DeferredImportSelector提供了新的方法getImportGroup()来跨DeferredImportSelector实现自定义Configuration的加载顺序。

3.5.3、@AutoConfigurationImportSelector功能概述

     下图展示了AutoConfigurationImportSelector的核心功能及流程,省略了外部通过@Import注解调用该类的部分:
在这里插入图片描述
     当AutoConfigurationImportSelector被@Import注解引入之后,它的selectImports方法会被调用并执行其实现的自动装配逻辑。selectImports方法几乎涵盖了组件自动装配的所有处理逻辑。AutoConfigurationImportSelector的selectImports方法源代码如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   
   //检测自动配置功能是否开启,默认为开启
   if (!this.isEnabled(annotationMetadata)) {
   
       return NO_IMPORTS;
   } else {
   
     //加载自动配置的原信息,配置文件为类路径中META-INF目录下的
     //spring-autoconfigure-metadata.properties文件
     AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
     //封装将被引用的自动配置信息
     AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
     //返回符合条件的配置类的全限定名数组
     return StringUtils.toStringArray(autoConfigurationEntry.
当阅读Spring Boot代码时,可以按照以下步骤进行: 1. 了解项目结构:Spring Boot项目通常遵循标准的MVC(Model-View-Controller)结构,其中包含控制器、服务、数据访问层等模块。首先,了解项目的整体结构有助于理解代码的组织方式。 2. 阅读启动类:Spring Boot应用程序的入口点是一个带有`@SpringBootApplication`注解的启动类。该类通常包含了主方法,用于启动应用程序。可以阅读该类来了解应用程序的配置和初始化过程。 3. 阅读配置文件:Spring Boot使用`application.properties`或`application.yml`等配置文件来配置应用程序。这些文件中包含了一些常用的配置项,如数据库连接、日志配置等。阅读配置文件可以了解应用程序的基本配置信息。 4. 阅读控制器:控制器负责处理HTTP请求,并将请求分派给相应的服务进行处理。通过阅读控制器代码,可以了解应用程序的请求处理逻辑。 5. 阅读服务:服务层是应用程序的核心业务逻辑部分,负责处理业务逻辑、调用数据访问层等。阅读服务层代码可以了解应用程序的核心功能和业务逻辑。 6. 阅读数据访问层:数据访问层负责与数据库进行交互,执行CRUD(创建、读取、更新、删除)操作。阅读数据访问层代码可以了解应用程序与数据库交互的方式和逻辑。 7. 阅读模型:模型类用于定义数据结构,通常与数据库中的表结构相对应。通过阅读模型类的代码,可以了解应用程序的数据结构和字段定义。 8. 阅读测试代码:Spring Boot鼓励进行单元测试和集成测试,测试代码通常位于与源代码相同的目录中。阅读测试代码可以了解应用程序的测试覆盖范围和测试用例。 在阅读代码时,可以结合官方文档、注释和命名规范等辅助信息来理解代码的含义和作用。还可以使用调试工具来跟踪代码的执行过程,以更深入地理解代码的运行流程。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值