乐于分享
好东西不私藏

Spring Boot 启动过程源码解析,看这篇就够了!

Spring Boot 启动过程源码解析,看这篇就够了!

        在本篇文章中,我们将深入探索 Spring Boot 的核心源码,了解它是如何实现自动配置的。通过源码分析,你将对 Spring Boot 有更深入的理解,同时也为后续学习 Spring Cloud 打下坚实基础。

1. SpringApplication.run 方法解析

1.1 为什么需要分析源码

为什么要读源码?

很多同学可能会问:”我会用 Spring Boot 不就行了,为什么要去看源码?”

其实,看源码有以下几个好处:

好处
说明
深入理解原理
明白自动配置是如何生效的,遇到问题能快速定位
学习优秀设计
Spring Boot 的代码质量很高,可以学习其中的设计模式
提升竞争力
面试中经常问到源码问题,掌握源码能增加面试通过率
解决疑难问题
当遇到常规方法无法解决的问题时,源码是最后的杀手锏

1.2 SpringApplication.run 源码分析

SpringApplication.run 是 Spring Boot 应用的入口方法,让我们一步步分析它做了什么。

// 这里是 SpringApplication 类的 run 方法// 第一个参数是主应用类(包含 main 方法的类)// 第二个参数是命令行参数publicstatic ConfigurableApplicationContext run(Class<?> primarySource,        String... args){// 这里调用了重载的 run 方法return run(new Class<?>[] { primarySource }, args);}

接下来看核心的 run 方法实现:

publicstatic ConfigurableApplicationContext run(Class<?>[] primarySources,        String[] args){// 1. 创建 SpringApplication 实例// 2. 执行 run 方法returnnew SpringApplication(primarySources).run(args);}

然后是真正的 run 方法执行流程:

public ConfigurableApplicationContext run(String... args){// 1. 创建并启动计时器,用于记录启动时间    StopWatch stopWatch = new StopWatch();    stopWatch.start();// 2. 初始化应用上下文和Environment    ConfigurableApplicationContext context = null;// 3. 配置 Spring Boot 的监听器集合    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();// 4. 设置 Headless 模式(在没有显示器的情况下也能运行)    configureHeadlessProperty();// 5. 获取并启动运行监听器    SpringApplicationRunListeners listeners = getRunListeners(args);    listeners.starting();try {// 6. 准备环境(ApplicationArguments)        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 7. 准备环境变量(PropertySources、Profile等)        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);// 8. 处理 Bean 的资源加载        configureIgnoreBeanInfo(environment);// 9. 打印 Banner(启动时的 Logo)        Banner printedBanner = printBanner(environment);// 10. 创建应用上下文        context = createApplicationContext();// 11. 准备异常报告器        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass<?>[] { ConfigurableApplicationContext.class }, context);// 12. 准备应用上下文(核心步骤!)        prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 13. 刷新应用上下文(核心步骤!加载 Bean)        refreshContext(context);// 14. 执行刷新后的操作        afterRefresh(context, applicationArguments);// 15. 停止计时器        stopWatch.stop();// 16. 输出启动日志if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass)                    .logStarted(getApplicationLog(), stopWatch);        }// 17. 发布应用启动事件        listeners.started(context);// 18. 执行 ApplicationRunner 和 CommandLineRunner        callRunners(context, applicationArguments);    }catch (Throwable ex) {// 如果启动失败,报告异常        handleRunFailure(context, ex, exceptionReporters, listeners);thrownew IllegalStateException(ex);    }try {// 19. 发布应用就绪事件        listeners.running(context);    }catch (Throwable ex) {        handleRunFailure(context, ex, exceptionReporters, null);thrownew IllegalStateException(ex);    }// 20. 返回应用上下文return context;}

流程图解:

┌─────────────────────────────────────────────────────────┐│                    Spring Boot 启动流程                   │├─────────────────────────────────────────────────────────┤│  1. StopWatch 计时器启动                                ││  2. 配置 Headless 模式                                  ││  3. 获取并启动监听器                                    ││  4. 准备 Environment(环境变量)                         ││  5. 打印 Banner                                        ││  6. 创建 ApplicationContext(应用上下文)                 ││  7. prepareContext(准备上下文) ← 重要                 ││  8. refreshContext(刷新上下文) ← 重要                 ││  9. 执行 Runner(ApplicationRunner/CommandLineRunner) ││  10. 发布启动完成事件                                    │└─────────────────────────────────────────────────────────┘

2. 自动配置原理分析

2.1 什么是自动配置

为什么需要自动配置?

在没有 Spring Boot 之前,我们需要编写大量的 XML 配置或者 Java 配置。比如要整合 MyBatis,我们需要:

// 传统 Spring 配置@ConfigurationpublicclassMyBatisConfig{// 配置数据源@Beanpublic DataSource dataSource(){returnnew DruidDataSource();    }// 配置 SqlSessionFactory@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource){        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();        factoryBean.setDataSource(dataSource);// 还需要配置 mapper 路径、类型别名等return factoryBean.getObject();    }// 配置 MapperScannerConfigurer@Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){        MapperScannerConfigurer configurer = new MapperScannerConfigurer();        configurer.setBasePackage("com.example.mapper");return configurer;    }}

而有了 Spring Boot,我们只需要引入依赖:

<!-- 只需要引入依赖,其他都自动配置好了 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency>

这就是自动配置的魔力!

2.2 @SpringBootApplication 注解解析

@SpringBootApplication 是 Spring Boot 应用的入口注解,让我们看看它的定义

@Target(ElementType.TYPE)  // 作用在类上@Retention(RetentionPolicy.RUNTIME)  // 运行时保留@Documented// 它是一个组合注解,由三个注解组成@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),    @Filter(type= FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class})public @interfaceSpringBootApplication{// ... 省略部分属性}

拆解来看:

注解
作用
@SpringBootConfiguration
标识这是一个配置类,本质上是 @Configuration
@EnableAutoConfiguration
开启自动配置,是核心注解
@ComponentScan
组件扫描,默认扫描当前包及其子包

2.3 @EnableAutoConfiguration 原理

@EnableAutoConfiguration 是自动配置的核心注解:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(AutoConfigurationImportSelector.class)  // 关键!导入自动配置选择器public @interfaceEnableAutoConfiguration{// 配置排除的自动配置类    Class<?>[] exclude() default {};// 根据名称排除    String[] excludeName() default {};// 配置名称String name()default "";}

AutoConfigurationImportSelector 实现了 ImportSelector 接口:

publicclassAutoConfigurationImportSelectorimplementsDeferredImportSelectorBeanClassLoaderAwareResourceLoaderAwareBeanFactoryAwareEnvironmentAwareOrdered{// 这个方法返回需要导入的配置类@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {// 如果没有启用自动配置,返回空数组if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;        }// 获取自动配置候选类        AutoConfigurationEntry entry = getAutoConfigurationEntry(annotationMetadata);// 返回需要导入的类名数组return entry.getConfigurations();    }// 获取自动配置条目protected AutoConfigurationEntry getAutoConfigurationEntry(            AnnotationMetadata annotationMetadata){// 1. 判断是否启用自动配置if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;        }// 2. 获取属性配置        AnnotationAttributes attributes = getAttributes(annotationMetadata);// 3. 获取所有候选配置类(核心方法!)        List<String> configurations = getCandidateConfigurations(            annotationMetadata, attributes);// 4. 去除重复的配置类        configurations = removeDuplicates(configurations);// 5. 处理排除的配置        Set<String> exclusions = getExclusions(annotationMetadata, attributes);        configurations.removeAll(exclusions);// 6. 过滤掉不满足条件 的配置类        configurations = filter(configurations, attributes);// 7. 触发自动配置导入事件        fireAutoConfigurationImportEvents(configurations, attributes);// 8. 排序并返回returnnew AutoConfigurationEntry(configurations, exclusions);    }// 获取候选配置类protected List<String> getCandidateConfigurations(            AnnotationMetadata metadata, AnnotationAttributes attributes){// 使用 SpringFactoriesLoader 加载配置// 这个方法会从 META-INF/spring.factories 文件中读取配置        List<String> 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;    }}

2.4 spring.factories 文件解析

Spring Boot 是如何知道要加载哪些自动配置类的?

答案是通过META-INF/spring.factories 文件!以 Spring Boot 内置的 mybatis-spring-boot-starter 为例:

# 文件位置:mybatis-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories# org.springframework.boot.autoconfigure.EnableAutoConfiguration 是自动配置的keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.boot.autoconfigure.MybatisAutoConfiguration,\org.mybatis.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration

Spring Boot 官方也定义了大量自动配置:

# Spring Boot 官方自动配置(部分)org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\...

3. 自动配置条件注解

3.1 条件注解的作用

为什么需要条件注解?

自动配置虽然方便,但不是所有场景都需要。比如:

场景
说明
数据库配置
如果没有引入数据库驱动,就不应该配置 DataSource
Web 配置
如果是普通 Java 项目,就不需要配置 Web 容器
Redis 配置
如果没有引入 Redis 依赖,就不应该配置 RedisTemplate

Spring Boot 通过条件注解来解决这个问题:

3.2 常用条件注解

注解
作用
@ConditionalOnClass
当类路径下存在指定类时,才配置 Bean
@ConditionalOnMissingClass
当类路径下不存在指定类时,才配置 Bean
@ConditionalOnBean
当容器中存在指定 Bean 时,才配置
@ConditionalOnMissingBean
当容器中不存在指定 Bean 时,才配置
@ConditionalOnProperty
当配置属性满足条件时,才配置
@ConditionalOnWebApplication
当是 Web 应用时,才配置

3.3 自动配置源码示例

以 Redis 自动配置为例:

// 文件位置:org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration// 标识这是一个配置类@Configuration(proxyBeanMethods = false)// 当类路径下存在 RedisOperations 类时才生效// 也就是引入了 spring-boot-starter-data-redis 依赖时@ConditionalOnClass(RedisOperations.class)// 当容器中没有 RedisTemplateBean 时才生效// 这样用户自定义的 RedisTemplate 会覆盖默认配置@ConditionalOnMissingBean(name"redisTemplate")// 启用 Redis 的配置属性@EnableConfigurationProperties(RedisProperties.class)// 导入配置类@Import({ LettuceConnectionConfiguration.classJedisConnectionConfiguration.class })publicclassRedisAutoConfiguration{// 配置 RedisTemplate@Bean@ConditionalOnMissingBean// 当没有 RedisTemplate 时才创建public RedisTemplate<Object, Object> redisTemplate(            RedisConnectionFactory connectionFactory){// 创建并配置 RedisTemplate        RedisTemplate<Object, Object> template = new RedisTemplate<>();        template.setConnectionFactory(connectionFactory);// 配置默认的序列化器// 使用 JDK 序列化器        template.setKeySerializer(new StringRedisSerializer());        template.setValueSerializer(new JdkSerializationRedisSerializer());// 配置 Hash 类型的序列化        template.setHashKeySerializer(new StringRedisSerializer());        template.setHashValueSerializer(new JdkSerializationRedisSerializer());        template.afterPropertiesSet();return template;    }// 配置 StringRedisTemplate(常用)@Bean@ConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(            RedisConnectionFactory connectionFactory){        StringRedisTemplate template = new StringRedisTemplate(connectionFactory);return template;    }}

4. 自动配置的执行时机

4.1 执行顺序

自动配置的执行顺序如下:

1.SpringBoot启动2.@SpringBootApplication扫描3.@EnableAutoConfiguration触发4.AutoConfigurationImportSelector执行5.加载spring.factories中的配置类6.根据条件注解判断是否生效7.执行配置类的@Bean方法8.应用启动完成

4.2 如何控制配置顺序

有时代我们需要控制自动配置的顺序,Spring Boot 提供了以下方式:

// 方式1:使用 @Order 注解// 指定 Bean 的加载顺序,数值越小越先加载@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public DataSource dataSource(){returnnew HikariDataSource();}// 方式2:使用 @AutoConfigureOrder// 指定自动配置类的加载顺序@Configuration@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)publicclassMyAutoConfiguration{// ...}// 方式3:使用 @AutoConfigureBefore / @AutoConfigureAfter// 指定在某个配置类之前/之后加载@Configuration@AutoConfigureBefore(WebMvcAutoConfiguration.class)publicclassMyWebAutoConfiguration{// ...}

5. 自定义自动配置

5.1 为什么要自定义自动配置

自定义自动配置的好处:

好处
说明
封装复杂配置
将复杂的配置封装成简单的 starter
复用配置
在多个项目中复用同一套配置
团队协作
团队成员只需引入依赖即可使用

5.2 自定义 starter 示例

假设我们要创建一个通用的文件上传 starter:

第一步:创建配置属性类

// 文件位置:file-spring-boot-starter/src/main/java/com/example/file/FileProperties.java// 启用配置属性绑定@ConfigurationProperties(prefix = "spring.file")publicclassFileProperties{// 文件存储路径private String storagePath = "/tmp/files";// 最大文件大小(默认 10MB)privatelong maxSize = 10485760;// 允许的文件类型private List<String> allowedTypes = Arrays.asList("jpg""png""pdf");// 是否启用privateboolean enabled = true;// Getter 和 Setter 省略...}

第二步:创建自动配置类

// 文件位置:file-spring-boot-autoconfigure/src/main/java/com/example/file/FileAutoConfiguration.java@Configuration// 条件:当类路径下存在 FileService 类时生效@ConditionalOnClass(FileService.class)// 绑定配置属性@EnableConfigurationProperties(FileProperties.class)publicclassFileAutoConfiguration{// 注入配置属性privatefinal FileProperties fileProperties;publicFileAutoConfiguration(FileProperties fileProperties){this.fileProperties = fileProperties;    }// 创建 FileService Bean@Bean// 条件:当容器中没有 FileService 时创建@ConditionalOnMissingBeanpublic FileService fileService(){returnnew FileServiceImpl(fileProperties);    }}

第三步:创建 spring.factories 文件

# 文件位置:file-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.file.FileAutoConfiguration

第四步:在 starter 中引用自动配置模块

<!-- file-spring-boot-starter/pom.xml --><dependencies><!-- 引入自动配置模块 --><dependency><groupId>com.example</groupId><artifactId>file-spring-boot-autoconfigure</artifactId><version>1.0.0</version></dependency></dependencies>

使用方式:

# 只需要在 application.yml 中配置即可spring:file:storage-path:/data/filesmax-size:20971520enabled:true

6. 总结

6.1 核心知识点

知识点
说明
SpringApplication.run
Spring Boot 启动入口
@EnableAutoConfiguration
启用自动配置的核心注解
AutoConfigurationImportSelector
自动配置导入选择器
spring.factories
存放自动配置类的文件
条件注解
控制自动配置的生效条件

6.2 启动流程图

┌──────────────────────────────────────────────────────────────────┐SpringBoot启动流程├──────────────────────────────────────────────────────────────────┤┌────────────────┐main方法入口└───────┬────────┘┌─────────────────────────────────────┐SpringApplication.run()-创建SpringApplication-执行run()方法└───────┬────────────────────────────┘┌─────────────────────────────────────┐@EnableAutoConfiguration-导入AutoConfigurationImportSelector└───────┬────────────────────────────┘┌─────────────────────────────────────┐加载spring.factories-读取自动配置类└───────┬────────────────────────────┘┌─────────────────────────────────────┐条件过滤-@ConditionalOnClass-@ConditionalOnBean└───────┬────────────────────────────┘┌─────────────────────────────────────┐注册Bean-执行@Bean方法-应用启动完成└─────────────────────────────────────┘└──────────────────────────────────────────────────────────────────┘

end

Spring Boot 整合 Swagger,看这篇就够了!

这是Spring Boot系列的第二十一篇,建议按顺序学习,逐步掌握Spring Boot核心知识点。
点点赞
点分享
点推荐
写留言
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Spring Boot 启动过程源码解析,看这篇就够了!

猜你喜欢

  • 暂无文章