专栏名称: 架构师
架构师云集,三高架构(高可用、高性能、高稳定)、大数据、机器学习、Java架构、系统架构、大规模分布式架构、人工智能等的架构讨论交流,以及结合互联网技术的架构调整,大规模架构实战分享。欢迎有想法、乐于分享的架构师交流学习。
目录
相关文章推荐
人人都是产品经理  ·  用户越“愚笨”,产品越硬核 ·  15 小时前  
人人都是产品经理  ·  成为高阶产品经理:必懂产品生命周期 ·  昨天  
产品犬舍  ·  纯银 2025 年的产品顾问合约更新 ·  2 天前  
三节课  ·  DeepSeek从入门到精通指南 ·  3 天前  
人人都是产品经理  ·  春招AI抢人大战:应届生年薪130万,实习月薪2万 ·  2 天前  
51好读  ›  专栏  ›  架构师

爆肝3万字,SpringBoot原理深入以及源码分析

架构师  · 公众号  ·  · 2025-01-24 22:28

正文

架构师(JiaGouX)
我们都是架构师!
架构未来,你来不来?




我们都知道, Spring 有它强大的地方,也有它繁琐的地方,毕竟如日中天的 Spring 全家桶太强大了,所以导致依赖各种 JAR 包维护起来费劲,还有编写各种 XML 配置文件。这两个痛点 SpringBoot 可以优雅的实现解决。背后当然是 SpringBoot 约定优于配置(Convention over Configuration) ,又称 按约定编程 ,是 一种软件设计范式 SpringBoot 是所有基于 Spring 开发的项目的起点。 SpringBoot 的设计就是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。

了解了 SpringBoot 出现的背景及优势后,我们就开始从下面几个方面来分析 SpringBoot 的原理以及源码分析。


一、依赖管理

问题1:为什么导入dependency时不需要指定版本?

在SpringBoot入门程序中,项目pom.xml文件中有两个核心依赖:

  • spring-boot-starter-parent
  • spring-boot-starter-web

1、spring-boot-starter-parent


<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.3.1.RELEASEversion>
    <relativePath/> 
parent>

我们再使用“ ctrl+鼠标左键 ”进入并查看 spring-boot-starter-parent 底层源文件,发现spring-boot-starter-parent的底层有一个父依赖 spring-boot-dependencies ,如下:

<parent>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-dependenciesartifactId>
  <version>2.3.1.RELEASEversion>
parent>

继续查看 spring-boot-dependencies 的源文件:


  5.15.12
  2.7.7
  1.9.80
  2.12.0
  1.9.5
  3.16.1
  4.0.6
  4.0.3
  2.1.4
  // ...

spring-boot-dependencies 的源文件可以看出,该文件通过标签对一些常用的技术框架的依赖文件进行了统一的版本号管理,例如 activemq、spring、tomcat 等,都有与 Spring Boot 2.3.1 版本相匹配的版本,这也是 pom.xml 引入依赖文件不需要标注依赖文件版本号的原因。

需要注意的是,如果pom.xml引入的依赖文件不是 spring-boot-starter-parent 管理的,那么在 pom.xml 引入依赖文件时,需要使用标签指定依赖文件的版本号。

问题2: spring-boot-starter-parent 父依赖启动器的主要作用是进行统一的版本管理,那么项目运行依赖的 JAR 包从何而来?

2 spring-boot-starter-web

查看 spring-boot-starter-web 的源文件:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-dependenciesartifactId>
      <version>2.3.1.RELEASEversion>
      <type>pomtype>
      <scope>importscope>
    dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starterartifactId>
    <version>2.3.1.RELEASEversion>
    <scope>compilescope>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jsonartifactId>
    <version>2.3.1.RELEASEversion>
    <scope>compilescope>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-tomcatartifactId>
    <version>2.3.1.RELEASEversion>
    <scope>compilescope>
dependency>
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webartifactId>
    <scope>compilescope>
dependency>
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webmvcartifactId>
    <scope>compilescope>
dependency>
dependencies>

Spring Boot 除了提供有上述介绍的 Web 依赖启动器外,还提供了其他许多开发场景的相关依赖。我们可以打开 Spring Boot 的官方文档,搜索 starters 关键字查询场景依赖启动器。

Spring Boot 并不是提供了所有场景的开发的技术框架都提供了场景启动器,例如 MyBatis、Druid 等。为了利用 Spring Boot 框架的优势,在 Spring Boot 官方没有整合这些技术框架的情况下, MyBatis、Druid 等技术框架所在的开发团队主动与 Spring Boot 框架进行整合,实现了各自的依赖启动器。


二、自动配置(启动流程)

概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目。

问题: Spring Boot 到底是如何进行自动配置的,都把哪些组件进行了自动配置?

Spring Boot 应用的启动入口是 @SpringBootApplication 注解标注类中的 main() 方法, @SpringBootApplication 能够扫描 Spring 组件并自动配置 Spring Boot

下面,查看 @SpringBootApplication 内部源码进行分析,核心代码具体图如下:

@SpringBootApplication
public class SpringBootExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootExampleApplication.classargs);
    }

}
@Target(ElementType.TYPE)    // 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime运行时
@Documented// 表示注解可以记录在javadoc中
@Inherited   // 表示可以被子类继承该注解

@SpringBootConfiguration// 标明该类为配置类
@EnableAutoConfiguration// 启动自动配置功能
@ComponentScan(excludeFilters = {   // 包扫描器
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  @Filter(type 
= FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication 
{

 ...

}

从上面源码可以看出, @SpringBootApplication 是一个组合注解,前面4个是注解的元数据信息,我们主要看后面3个注解: @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个核心注解,关于这三个核心注解说明具体如下:

1、@SpringBootConfiguration

@SpringBootConfiguration 注解表示 Spring Boot 配置类。查看 @SpringBootConfiguration 注解源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration //配置类
public @interface SpringBootConfiguration {

}

可以看出, @SpringBootConfiguration 注解内部有一个核心注解 @Configuration ,该注解是由Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解形式),并可以被组件扫描器扫描。由此可见, @SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已。

2、@EnableAutoConfiguration

@EnableAutoConfiguration 注解表示开启自动配置功能,该注解是 Spring Boot 框架最重要的注解,也是实现自动化配置的注解。我们同样来查看该注解的源代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage// 自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
@Import(AutoConfigurationImportSelector.class)  // 可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
public @interface EnableAutoConfiguration 
{

 String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

 Class>[] exclude() default {};

 String[] excludeName() default {};

}

下面,对这两个核心注解分别讲解:

2.1 @AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

// spring框架的底层注解,它的作用就是给容器中导入某个组件类,
// 例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中
@Import(AutoConfigurationPackages.Registrar.class)  // 默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中
public @interface AutoConfigurationPackage 
{

}

@Import(AutoConfigurationPackages.Registrar.class) ,它就是将 Registrar 这个组件类导入到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法,这个方法就是导入组件类的具体实现。

static class Registrar implements ImportBeanDefinitionRegistrarDeterminableImports {

// 获取的是项目主程序启动类所在的目录
// metadata:注解标注的元数据信息
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
  register(registry, new PackageImport(metadata).getPackageName());
 }

@Override
public Set determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
 }

}

使用debug跟踪可以发现 new PackageImport(metadata).getPackageName() 就是 com.riemann

也就是说 @AutoConfigurationPackage 注解的主要的作用就是将主程序类所在包及所有子包下的组件扫描到 Spring 容器中。

因此,在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类运行业务开发,这样才能保证定义的类能够被组件扫描器扫描。

2.2 @Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector 这个类导入到 Spring 容器中, AutoConfigurationImportSelector 可以帮助 Spring Boot 应用将所有符合条件的 @Configuration 配置都加载到当前 Spring Boot 创建并使用的 IoC 容器( ApplicationContext )中,通过源码分析这个类是通过 selectImports 这个方法告诉 Spring Boot 都需要导入哪些组件:

// 这个方法告诉springboot都需要导入那些组件
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
 }
// 1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
// 作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
// 自动配置的类全名.条件=值
 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

深入研究 loadMetadata 方法:

final class AutoConfigurationMetadataLoader {

protectedstaticfinal String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"// 文件中为需要加载的配置类的类路径

private AutoConfigurationMetadataLoader() {
 }

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
// 重载方法
return loadMetadata(classLoader, PATH);
 }

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
   // 1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
            // 获得 PATH 对应的 URL 们
   Enumeration urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
            // 遍历 URL 数组,读取到 properties 中
            Properties properties = new Properties();

   // 2.解析urls枚举对象中的信息封装成properties对象并加载
   while (urls.hasMoreElements()) {
    properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
   }
   // 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象

   // 根据封装好的properties对象生成AutoConfigurationMetadata对象返回
   return loadMetadata(properties);
  } catch (IOException ex) {
   thrownew IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
  }
 }
 ...
}

深入研究 getAutoConfigurationEntry 方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // 1. 判断是否开启注解。如未开启,返回空串
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
 }
// 2. 获得注解的属性
 AnnotationAttributes attributes = getAttributes(annotationMetadata);

// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
 List configurations = getCandidateConfigurations(annotationMetadata, attributes);


// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
 configurations = removeDuplicates(configurations);
// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
 Set exclusions = getExclusions(annotationMetadata, attributes);
// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
 checkExcludedClasses(configurations, exclusions);
// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
 configurations.removeAll(exclusions);

// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
//@ConditionalOnMissingClass : classpath中不存在该类时起效
//@ConditionalOnBean : DI容器中存在该类型Bean时起效
//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
//@ConditionalOnExpression : SpEL表达式结果为true时
//@ConditionalOnProperty : 参数设置或者值一致时起效
//@ConditionalOnResource : 指定的文件存在时起效
//@ConditionalOnJndi : 指定的JNDI存在时起效
//@ConditionalOnJava : 指定的Java版本存在时起效
//@ConditionalOnWebApplication : Web应用环境下起效
//@ConditionalOnNotWebApplication : 非Web应用环境下起效

// 总结一下判断是否要加载某个类的两种方式:
// 根据spring-autoconfigure-metadata.properties进行判断。
// 要判断@Conditional是否满足
// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
 configurations = filter(configurations, autoConfigurationMetadata);


// 6. 将自动配置导入事件通知监听器
// 当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
// 并触发fireAutoConfigurationImportEvents事件。
 fireAutoConfigurationImportEvents(configurations, exclusions);
// 7. 创建 AutoConfigurationEntry 对象
returnnew AutoConfigurationEntry(configurations, exclusions);
}

getAutoConfigurationEntry 里有一个重要的方法 getCandidateConfigurations ,这个方法是让 SpringFactoriesLoader 去加载一些组件的名字。

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 让SpringFactoryLoader去加载一些组件的名字
 List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// 断言,非空
 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
return configurations;
}

protected Class> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}

protected ClassLoader getBeanClassLoader() {
returnthis.beanClassLoader;
}

该方法主要市区加载一个外部文件,该文件在如下图:

@EnableAutoConfiguration 就是从 classpath 中搜寻 META-INF/spring.factories 配置文件,并将其中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项通过反射( Java Reflection )实例化为对应的标注了 @Configuration的JavaConfig 形式的配置类,并加载到 IoC 容器中。

直到看到了 spring.factories 配置文件对应的那些类才明白了 Spring Boot 为啥不需要配置各种 XML 文件了。那是因为这些配置类的本质是传统 Spring MVC 框架中对应的 XML 配置文件,只不过在 Spring Boot 中以自动配置类的形式进行了预先配置。

因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改。

2.3 小结

Spring Boot 底层实现自动配置的步骤是:

  1. Spring Boot 应用启动;
  2. @SpringBootApplication 起作用;
  3. @SpringBootConfiguration ,标明该类为配置类;
  4. @EnableAutoConfiguration






请到「今天看啥」查看全文


推荐文章
人人都是产品经理  ·  用户越“愚笨”,产品越硬核
15 小时前
人人都是产品经理  ·  成为高阶产品经理:必懂产品生命周期
昨天
产品犬舍  ·  纯银 2025 年的产品顾问合约更新
2 天前
三节课  ·  DeepSeek从入门到精通指南
3 天前
人人都是产品经理  ·  春招AI抢人大战:应届生年薪130万,实习月薪2万
2 天前
治愈系心理学  ·  陪孩子在错误里多呆一会儿
8 年前
北京吃货小分队  ·  北京最豪华手抓饭!10个人也吃不完
8 年前