SpringBoot底层原理详解

O泡李华 6

SpringBoot 底层原理详解

本章位置:第二阶段 Java 核心框架
前置知识:SpringBoot 请求响应、RESTful、MyBatis、事务、AOP、反射、注解
下一篇:Maven 高级
学习目标:从“会使用 SpringBoot”进入“理解 SpringBoot 为什么能自动工作”,掌握 IOC、Bean、依赖注入、组件扫描、自动配置、条件装配、Starter、Bean 生命周期、SpringApplication 启动流程、内嵌 Tomcat 和 DispatcherServlet 的底层关系。


一、为什么要学 SpringBoot 底层原理

前面我们已经会:

@SpringBootApplication

@RestController

@Service

@Mapper

@Autowired

@Transactional

@RequestMapping

但如果只会:

复制注解

遇到下面这些问题就很容易迷糊:

为什么 Controller 会自动被发现?

为什么 Service 不需要自己 new?

为什么 MyBatis Mapper 可以直接注入?

为什么配置一个 starter 就能使用某项功能?

为什么 SpringBoot 能自动启动 Tomcat?

为什么 application.yml 能改变配置?

为什么有些 Bean 有、有些 Bean 没有?

为什么 @ConditionalOnMissingBean 可以“你没有我才帮你创建”?

为什么改包结构以后 Controller 404?

为什么一个 Bean 可以被 AOP 代理?

SpringApplication.run() 到底做了什么?

所以这一章目标是:

把 SpringBoot 黑盒拆开

二、先区分 Spring、Spring MVC、SpringBoot

这三个不要混淆。


三、Spring Framework

Spring Framework 提供核心能力:

IOC

DI

AOP

事务

事件

资源管理

Web MVC

SpringBoot:

建立在 Spring Framework 之上

四、Spring MVC

Spring MVC 是 Spring 的:

Web MVC 框架

主要负责:

HTTP 请求

DispatcherServlet

Controller

参数绑定

响应转换

异常处理

五、SpringBoot

SpringBoot 的核心目标:

简化 Spring 项目配置和启动

它没有把 Spring 替换掉。

而是:

约定优于配置

自动配置

Starter

内嵌服务器

快速启动

六、三者关系

Spring Framework
│
├─ IOC
├─ AOP
├─ Transaction
└─ Spring MVC
    └─ Web
         ↑
         │
SpringBoot
负责快速装配、自动配置、启动

七、SpringBoot 最大的两个关键词

最重要:

自动配置

约定优于配置

八、没有 SpringBoot 的传统 Spring

以前可能需要配置:

XML Bean

DispatcherServlet

ViewResolver

DataSource

TransactionManager

JSON Converter

Tomcat Deployment

配置很多。


九、有 SpringBoot 后

很多东西:

自动创建

自动推断

自动装配

开发者更多关注:

业务代码

十、SpringBoot 不是“什么都自动”

它的自动配置有条件。

也就是:

符合条件
才配置

这就是后面的:

Conditional
条件装配

十一、IOC 是什么

IOC:

Inversion of Control

中文:

控制反转

十二、什么叫控制反转

以前:

UserService service =
        new UserService();

对象由:

程序员自己创建

现在:

@Service
public class UserService {

}

Spring:

帮你创建对象
帮你保存对象
帮你管理对象

对象控制权:

从程序员
转交给 Spring 容器

这叫:

IOC

十三、IOC 容器

Spring 中管理对象的容器:

ApplicationContext

可以简单理解:

一个高级 Bean 工厂

里面保存:

Bean

十四、Bean 是什么

Spring Bean:

被 Spring 容器创建和管理的 Java 对象。

例如:

@Service
public class UserService {

}

UserService 实例:

就是一个 Bean

十五、哪些对象可以成为 Bean

常见:

@Controller

@RestController

@Service

@Repository

@Component

@Configuration

标记的类。

也可以:

@Bean

手工声明。


十六、@Component

最基础组件注解:

@Component
public class EmailUtil {

}

表示:

让 Spring 管理这个类

十七、@Service

语义上表示:

业务层 Bean

本质:

属于组件注解体系

十八、@Repository

语义上表示:

持久层组件

并且在 Spring 数据访问体系中还有:

异常转换

相关语义。


十九、@Controller

表示:

Spring MVC Controller

二十、@RestController

可以理解:

@Controller
+
@ResponseBody

二十一、@Configuration

表示:

配置类

通常用来:

声明 @Bean

添加框架配置

二十二、@Bean

如果某个类:

不能直接加 @Component

例如第三方库:

ObjectMapper

PasswordEncoder

某 SDK Client

可以:

@Configuration
public class AppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();
    }
}

二十三、@Bean 方法返回值

Spring 会:

调用方法

取得返回对象

注册到 IOC 容器

Bean 名默认常见:

方法名

例如:

passwordEncoder

二十四、IOC 和 DI 的关系

IOC:

对象由 Spring 管理

DI:

Spring 把对象依赖注入给另一个对象

二十五、DI 是什么

DI:

Dependency Injection

中文:

依赖注入

二十六、传统写法

public class UserService {

    private UserMapper userMapper =
            new UserMapperImpl();

}

UserService:

自己创建依赖

耦合高。


二十七、Spring 写法

@Service
public class UserService {

    private final UserMapper
            userMapper;

    public UserService(
            UserMapper userMapper
    ) {

        this.userMapper =
                userMapper;
    }
}

Spring:

从容器找到 UserMapper Bean
↓
传进构造器

二十八、构造器注入

推荐:

private final UserMapper userMapper;

public UserService(
        UserMapper userMapper
) {

    this.userMapper =
            userMapper;
}

二十九、为什么推荐构造器注入

优点:

依赖明确

字段可以 final

方便测试

对象创建后依赖完整

避免隐藏依赖

三十、字段注入

以前常见:

@Autowired
private UserMapper userMapper;

虽然能用,

但现在更推荐:

构造器注入

三十一、只有一个构造器时

现代 Spring:

通常不需要写 @Autowired

Spring 会自动:

选择唯一构造器

三十二、DI 按什么找 Bean

常见:

按类型

例如:

UserMapper userMapper

Spring 找:

UserMapper 类型 Bean

三十三、如果同类型有两个 Bean

例如:

PaymentService

实现:

AliPayService

WechatPayService

注入:

PaymentService

Spring 不知道选谁。

可能:

NoUniqueBeanDefinitionException

三十四、@Primary

可以:

@Primary
@Service
public class AliPayService
        implements PaymentService {

}

表示:

默认优先

三十五、@Qualifier

也可以:

public OrderService(
        @Qualifier(
                "wechatPayService"
        )
        PaymentService paymentService
) {

}

指定 Bean。


三十六、Bean 名

默认:

类名首字母小写

例如:

UserService
→ userService

三十七、ApplicationContext 获取 Bean

虽然业务中不推荐到处手工获取,

但为了理解:

ApplicationContext context =
        SpringApplication.run(
                DemoApplication.class,
                args
        );

UserService userService =
        context.getBean(
                UserService.class
        );

三十八、说明什么

说明:

SpringApplication.run()

返回的是:

ApplicationContext

即:

Spring IOC 容器

三十九、组件扫描是什么

Spring 不可能:

扫描整个硬盘所有 Java 类

它只扫描:

指定包范围

找到:

组件注解

注册 Bean。


四十、@ComponentScan

用于:

组件扫描

例如:

@ComponentScan(
        "com.example"
)

四十一、SpringBoot 为什么不用我们写 ComponentScan

因为:

@SpringBootApplication

本身包含:

@ComponentScan

相关功能。


四十二、启动类位置为什么重要

推荐:

启动类放根包

例如:

com.example.demo
├─ DemoApplication
├─ controller
├─ service
├─ mapper
└─ config

四十三、错误包结构

例如:

com.example.app
└─ DemoApplication

com.other.controller
└─ UserController

默认扫描:

可能扫不到 com.other

于是:

Controller 没 Bean
→ 请求 404

四十四、@SpringBootApplication

这是 SpringBoot 最核心注解之一。

@SpringBootApplication
public class DemoApplication {

}

四十五、@SpringBootApplication 大致包含

可以理解为组合:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

四十六、@SpringBootConfiguration

本质上属于:

@Configuration

语义:

当前类是 SpringBoot 主配置类

四十七、@EnableAutoConfiguration

表示:

开启自动配置

这是 SpringBoot 最核心的能力之一。


四十八、@ComponentScan

负责:

扫描用户自己写的组件

四十九、自动配置 vs 组件扫描

组件扫描:

找到你自己写的 Bean

自动配置:

根据依赖和条件
帮你创建框架 Bean

不要混淆。


五十、组件扫描例子

你写:

@Service
public class UserService {

}

通过:

Component Scan

注册。


五十一、自动配置例子

你引入:

spring-boot-starter-web

SpringBoot 检测到:

Spring MVC

Servlet

Jackson

Tomcat

然后自动配置:

DispatcherServlet

MessageConverter

Web MVC 基础设施

内嵌服务器

五十二、自动配置本质

可以理解:

SpringBoot 内部提前写了很多配置类

例如:

WebMvcAutoConfiguration

JacksonAutoConfiguration

DataSourceAutoConfiguration

满足条件时:

导入
创建 Bean

五十三、自动配置不是“魔法”

它本质仍然:

@Configuration

@Bean

@Conditional...

只是:

框架提前帮你写好

五十四、条件装配是什么

条件装配:

满足某个条件时,才注册 Bean。

常见注解:

@ConditionalOnClass

@ConditionalOnMissingBean

@ConditionalOnBean

@ConditionalOnProperty

@ConditionalOnWebApplication

五十五、@ConditionalOnClass

意思:

classpath 中存在某个类
才配置

例如:

有 Jackson
才做 Jackson 自动配置

五十六、classpath 是什么

运行时:

JVM 可以找到的类路径

Maven 依赖:

下载 jar
↓
加入 classpath

于是 SpringBoot 可以检测:

某个类是否存在

五十七、@ConditionalOnMissingBean

意思:

容器里没有这个 Bean
SpringBoot 才帮你创建

五十八、这是“用户配置优先”的关键

例如 SpringBoot:

默认提供 ObjectMapper

但如果你自己:

@Bean
public ObjectMapper objectMapper() {

    ...
}

自动配置通常:

退让

不再创建默认 Bean。


五十九、为什么说“约定优于配置”

默认情况:

你什么都不配置
也能运行

如果你需要自定义:

覆盖部分默认行为

六十、@ConditionalOnBean

意思:

容器已经存在某个 Bean
才启用当前配置

六十一、@ConditionalOnProperty

根据:

配置文件属性

决定是否启用。

例如:

feature.sms.enabled=true

才启用短信 Bean。


六十二、@ConditionalOnWebApplication

只有:

Web 应用

才装配。


六十三、Starter 是什么

Starter:

一组依赖的“启动依赖包”

核心作用:

帮你一次性引入一组相关依赖

六十四、spring-boot-starter-web

引入后常包含:

Spring MVC

Tomcat

Jackson

Validation/Logging 的相关基础依赖

具体传递依赖随版本可能变化。


六十五、Starter 本身不等于自动配置

Starter 主要:

解决依赖组合

AutoConfiguration:

解决 Bean 配置

二者一起:

构成 SpringBoot 快速开发体验

六十六、Starter + AutoConfiguration

流程:

添加 Starter
↓
相关类进入 classpath
↓
自动配置类判断条件
↓
条件满足
↓
创建 Bean

六十七、为什么加 starter 就能用 Web

因为:

Web 相关类进入 classpath

自动配置检测到:

Web 环境

于是:

装配 Web 基础设施

六十八、SpringBoot 3 自动配置文件

现代 SpringBoot 中自动配置元数据通常使用:

META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports

列出:

自动配置类

六十九、为什么要知道这个

当以后自己写:

Starter

就会用到。


七十、以前的 spring.factories

较老 SpringBoot 版本常通过:

META-INF/spring.factories

发现自动配置。

现代版本:

自动配置导入机制已经演进

所以学习时不要只背老版本写法。


七十一、AutoConfiguration 类

现代写法常见:

@AutoConfiguration
public class XxxAutoConfiguration {

}

内部再:

@Bean

@Conditional...

七十二、自动配置简化示例

假设我们写短信功能:

@AutoConfiguration
@ConditionalOnClass(
        SmsClient.class
)
public class SmsAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SmsService smsService() {

        return new SmsService();
    }
}

七十三、逻辑

有 SmsClient 类
↓
用户没自己定义 SmsService
↓
SpringBoot 自动创建 SmsService

七十四、application.yml 为什么能配置 Bean

因为 SpringBoot 建立:

Environment

统一读取:

application.properties

application.yml

环境变量

命令行参数

七十五、Environment

Spring 中可以:

private final Environment
        environment;

读取:

environment.getProperty(
        "server.port"
);

七十六、配置优先级

不同来源:

配置文件

环境变量

命令行参数

有:

优先级规则

SpringBoot 会合并成:

Environment

七十七、@Value

例如:

@Value(
        "${app.name}"
)
private String appName;

七十八、@ConfigurationProperties

大量配置更推荐:

@ConfigurationProperties(
        prefix = "app"
)
public class AppProperties {

    private String name;

    private Integer timeout;

}

七十九、为什么 ConfigurationProperties 更适合复杂配置

相比多个:

@Value

它:

类型安全

结构清晰

支持嵌套配置

便于复用

八十、配置绑定本质

SpringBoot:

读取 Environment
↓
属性名匹配
↓
类型转换
↓
绑定 JavaBean

这又用到了:

反射

JavaBean

类型转换

八十一、BeanDefinition 是什么

Spring 创建 Bean 前:

先保存“Bean 的定义信息”

这个元数据可以理解为:

BeanDefinition

八十二、BeanDefinition 保存什么

例如:

Bean 类型

Bean 名

作用域

构造方式

初始化方法

是否懒加载

依赖关系

八十三、Spring 启动并不是先把所有对象直接 new 出来

大致:

扫描类
↓
注册 BeanDefinition
↓
执行 BeanFactory 后处理
↓
创建 Bean
↓
依赖注入
↓
初始化

八十四、BeanFactory

Spring 最核心的 Bean 容器接口之一:

BeanFactory

主要:

创建 Bean

获取 Bean

管理 Bean

八十五、ApplicationContext 和 BeanFactory

ApplicationContext:

建立在 BeanFactory 之上

并扩展:

事件

国际化

资源

环境

Web 上下文

所以业务项目常接触:

ApplicationContext

八十六、Bean 的作用域

常见:

singleton

prototype

request

session

八十七、singleton

Spring 默认:

单例作用域

意思:

一个 ApplicationContext 中
通常一个 Bean 实例

八十八、这就是为什么 Controller 不能保存当前用户

Controller 默认:

singleton

多个用户:

共享同一个 Controller 对象

所以请求数据不能:

放成员变量

八十九、prototype

每次获取 Bean:

创建新实例

九十、request scope

一个 HTTP 请求:

一个 Bean 实例

九十一、session scope

一个 Session:

一个 Bean 实例

九十二、普通 Service 为什么适合 singleton

因为 Service 应该设计为:

无状态

请求数据:

只放局部变量

九十三、Bean 生命周期

这是 Spring 面试高频。

大致:

实例化

属性填充 / 依赖注入

Aware 回调

BeanPostProcessor 前置

初始化

BeanPostProcessor 后置

使用 Bean

销毁

九十四、第一步:实例化

Spring:

选择构造器
↓
创建对象

九十五、第二步:依赖注入

例如:

UserService
需要 UserMapper

Spring:

把 UserMapper 注入

九十六、Aware

某些 Bean 可以通过:

BeanNameAware

ApplicationContextAware

获得容器相关信息。


九十七、BeanPostProcessor

非常重要。

BeanPostProcessor:

Bean 初始化前后
可以统一处理 Bean

九十八、AOP 为什么和 BeanPostProcessor 有关系

Spring AOP 代理对象的创建:

和 Bean 后置处理机制密切相关

可以理解:

Spring 创建原始 Bean
↓
后处理器判断是否需要 AOP
↓
如果需要
创建 Proxy
↓
容器最终暴露 Proxy

九十九、这就解释了为什么注入的 Service 可能是代理对象

你写:

UserService

容器实际给 Controller 的可能是:

UserService Proxy

于是:

@Transactional
@Around

才能生效。


一百、@PostConstruct

Bean 依赖注入完成后,

可以:

@PostConstruct
public void init() {

}

用于初始化。

SpringBoot 3 使用:

jakarta.annotation.PostConstruct

一百零一、@PreDestroy

Bean 销毁前:

@PreDestroy
public void destroy() {

}

用于:

释放资源

一百零二、@Bean initMethod

也可以:

@Bean(
        initMethod = "init",
        destroyMethod = "close"
)

一百零三、InitializingBean

还可以实现:

InitializingBean

但业务代码更常用:

@PostConstruct

或:

@Bean initMethod

一百零四、Bean 生命周期为什么重要

因为很多框架功能依赖:

Bean 创建过程

例如:

AOP

事务代理

ConfigurationProperties

@Autowired

事件监听

一百零五、循环依赖是什么

例如:

A 依赖 B

B 又依赖 A

一百零六、构造器循环依赖

A(
    B b
)

B(
    A a
)

创建 A:

需要 B

创建 B:

又需要 A

无法完成。


一百零七、为什么不要依赖 Spring “自动解决”循环依赖

循环依赖往往意味着:

设计职责不清

最佳方案:

重构

一百零八、SpringBoot 2.6+ 对循环依赖更严格

现代 SpringBoot 默认:

更倾向不允许循环引用

所以不要依赖:

旧项目里某些 setter 循环依赖还能跑

一百零九、解决循环依赖

优先:

重新拆 Service

提取公共 Service

调整职责

而不是:

到处 @Lazy

一百一十、@Lazy

可以:

@Lazy

延迟创建 Bean。

但它不是:

循环依赖设计问题的万能解

一百一十一、Bean 什么时候创建

默认 singleton:

ApplicationContext refresh 时
通常提前创建

除非:

lazy

一百一十二、SpringBoot 启动入口

SpringApplication.run(
        DemoApplication.class,
        args
);

这是:

整个 SpringBoot 启动核心入口

一百一十三、SpringApplication.run() 大致做什么

可以简化成:

1. 创建 SpringApplication

2. 判断应用类型

3. 准备 Environment

4. 创建 ApplicationContext

5. 准备 Context

6. 加载 BeanDefinition

7. refresh Context

8. 创建 Bean

9. 启动内嵌 Web Server

10. 执行 Runner

11. 应用启动完成

一百一十四、创建 SpringApplication

内部会读取:

主启动类

例如:

DemoApplication.class

一百一十五、判断 WebApplicationType

SpringBoot 会判断:

Servlet Web

Reactive Web

Non-Web

例如引入:

spring-boot-starter-web

通常识别为:

SERVLET

一百一十六、准备 Environment

读取:

系统属性

环境变量

application.yml

application.properties

命令行参数

合并配置。


一百一十七、创建 ApplicationContext

Servlet Web 应用:

会使用适合 Web 的 ApplicationContext

一百一十八、加载主配置类

也就是:

@SpringBootApplication
public class DemoApplication {

}

一百一十九、refresh()

这是 Spring 容器非常核心的方法。

大量工作都在:

ApplicationContext.refresh()

期间发生。


一百二十、refresh 做什么

高度简化:

准备 BeanFactory

执行 BeanFactoryPostProcessor

注册 BeanPostProcessor

初始化事件机制

创建 singleton Bean

启动相关生命周期组件

一百二十一、BeanFactoryPostProcessor

它处理:

BeanDefinition

发生在:

Bean 真正创建之前

一百二十二、ConfigurationClassPostProcessor

非常重要。

负责处理:

@Configuration

@ComponentScan

@Import

@Bean

等配置类机制。


一百二十三、这就是注解配置能工作的关键之一

启动类:

@SpringBootApplication

会被:

ConfigurationClassPostProcessor

解析。


一百二十四、@Import

可以:

把其他配置类 / Bean 定义导入容器

自动配置也大量使用:

Import

思想。


一百二十五、Enable 注解模式

Spring 中很多:

@EnableXxx

本质常见:

@Import

导入某些配置。


一百二十六、@EnableAutoConfiguration

也会触发:

自动配置导入机制

一百二十七、自动配置选择过程

简化:

读取自动配置候选类
↓
应用条件过滤
↓
留下匹配配置
↓
注册 BeanDefinition
↓
创建 Bean

一百二十八、为什么 SpringBoot 启动日志里自动配置很多

因为内部:

检查大量条件

但真正创建的:

只是匹配当前项目的部分

一百二十九、Condition Evaluation Report

SpringBoot 可以输出:

条件评估报告

帮助查看:

哪些 AutoConfiguration 生效

哪些没生效

为什么

一百三十、debug 模式

可以配置:

debug=true

启动时可能显示:

CONDITIONS EVALUATION REPORT

一百三十一、Positive matches

表示:

满足条件
生效的自动配置

一百三十二、Negative matches

表示:

条件不满足
没有启用

一百三十三、排查自动配置问题

如果:

Bean 为什么没有?

可以检查:

有没有 starter

类是否在 classpath

属性是否正确

是否存在用户自定义 Bean

条件评估报告

一百三十四、内嵌 Tomcat 为什么自动启动

因为引入:

spring-boot-starter-web

后:

Tomcat 相关类在 classpath

自动配置发现:

Servlet Web Application

于是创建:

Servlet Web Server Factory

一百三十五、TomcatServletWebServerFactory

可以理解:

负责创建内嵌 Tomcat

一百三十六、SpringBoot 传统项目不需要外部 Tomcat

因为:

Tomcat 作为依赖
包含在应用中

执行:

main()

即可启动。


一百三十七、传统 WAR vs SpringBoot JAR

传统:

项目
↓
打 WAR
↓
外部 Tomcat

SpringBoot 常见:

项目
↓
可执行 JAR
↓
java -jar
↓
内嵌 Tomcat

一百三十八、可执行 JAR

SpringBoot Maven 插件会:

重新打包

让 JAR:

包含应用代码

依赖

启动信息

可以:

java -jar app.jar

一百三十九、SpringBoot Loader

SpringBoot 可执行 Jar 需要:

特殊加载机制

让嵌套依赖:

能够被加载

一百四十、为什么普通 JAR 不会直接包含依赖

普通 Maven JAR:

通常只包含项目 class/resources

依赖还在:

外部 classpath

SpringBoot repackage:

生成可执行结构

一百四十一、Spring Boot Maven Plugin

pom 常见:

<plugin>

    <groupId>
        org.springframework.boot
    </groupId>

    <artifactId>
        spring-boot-maven-plugin
    </artifactId>

</plugin>

用于:

SpringBoot 打包

一百四十二、DispatcherServlet 怎么来的

你没有手工:

new DispatcherServlet()

但 SpringBoot 自动配置:

帮你创建和注册

一百四十三、DispatcherServlet 核心作用

所有 Spring MVC 请求统一入口

流程:

Tomcat
↓
DispatcherServlet
↓
HandlerMapping
↓
HandlerAdapter
↓
Controller
↓
HttpMessageConverter
↓
Response

一百四十四、HandlerMapping

负责:

根据 URL + Method
找到哪个 Controller 方法

一百四十五、RequestMappingHandlerMapping

会扫描:

@Controller

@RestController

中的:

@RequestMapping

@GetMapping

@PostMapping

建立映射。


一百四十六、映射表

例如:

@GetMapping(
        "/users/{id}"
)

Spring 启动时:

注册映射信息

请求到来:

快速找到对应 HandlerMethod

一百四十七、HandlerMethod

表示:

Controller Bean
+
具体 Method

这也是前面 Interceptor 中:

handler instanceof HandlerMethod

的来源。


一百四十八、HandlerAdapter

找到 Handler 后:

负责调用它

并处理:

参数解析
返回值处理

一百四十九、参数解析

例如:

@PathVariable

@RequestParam

@RequestBody

@RequestHeader

都是由不同:

HandlerMethodArgumentResolver

处理。


一百五十、HandlerMethodArgumentResolver

可以理解:

“这个参数我会解析”

例如:

@PathVariable

有对应 resolver。


一百五十一、这就是 Spring MVC 参数绑定为什么这么灵活

Controller 方法:

public Result<?> detail(
        @PathVariable Long id,
        @RequestHeader String token
)

Spring:

逐个找适合的参数解析器

一百五十二、@RequestBody 怎么解析

会使用:

HttpMessageConverter

例如:

MappingJackson2HttpMessageConverter

把:

JSON
→ Java Object

一百五十三、返回对象怎么变 JSON

Controller:

return userVO;

Spring:

返回值处理器
↓
HttpMessageConverter
↓
Jackson
↓
JSON

一百五十四、Jackson 为什么自动存在

因为:

Web Starter 带相关依赖

并且:

JacksonAutoConfiguration

等自动配置满足条件。


一百五十五、DataSource 为什么自动创建

当项目:

有 JDBC / MySQL Driver

并配置:

spring.datasource.url

username

password

SpringBoot 自动配置:

DataSource

一百五十六、DataSourceAutoConfiguration

就是典型:

自动配置类

根据条件:

创建 DataSource

一百五十七、连接池

SpringBoot 默认常见使用:

HikariCP

前提:

对应依赖和自动配置条件满足

一百五十八、为什么 MyBatis Mapper 能自动注入

MyBatis Spring Starter 会:

扫描 Mapper

创建 Mapper Proxy

注册为 Spring Bean

于是 Service:

public UserService(
        UserMapper userMapper
)

可以直接注入。


一百五十九、Mapper 不是普通实现类

接口:

public interface UserMapper {

}

真正注入的是:

MyBatis 创建的代理对象

一百六十、Spring 和 MyBatis 怎么结合

大致:

Spring IOC
管理 Mapper Proxy
↓
Mapper Proxy
使用 SqlSessionTemplate
↓
SqlSessionTemplate
参与 Spring Transaction
↓
JDBC Connection

一百六十一、SqlSessionTemplate

Spring-MyBatis 集成中的重要类。

它:

线程安全地适配 SqlSession

并和:

Spring 事务

集成。


一百六十二、为什么 Spring 项目不自己保存 SqlSession

因为:

SqlSessionTemplate
+
Spring Transaction

已经负责上下文管理。


一百六十三、事务 Bean 怎么自动出现

SpringBoot / Spring Framework 根据:

DataSource

事务管理器

@EnableTransactionManagement 等机制

建立:

事务 AOP

于是:

@Transactional

能够工作。


一百六十四、AOP 代理什么时候创建

Bean 生命周期中:

BeanPostProcessor

会判断某个 Bean:

是否需要代理

例如发现:

@Transactional

可能创建:

Proxy

一百六十五、最终注入的是谁

可能不是:

原始 UserService

而是:

UserService Proxy

一百六十六、这解释 self-invocation

外部:

Controller
↓
Proxy
↓
Transactional
↓
Target

内部:

Target.this.method()

直接:

Target

绕过:

Proxy

一百六十七、Spring 事件机制

ApplicationContext 还支持:

ApplicationEvent

和:

@EventListener

一百六十八、事件适合什么

例如:

用户注册成功
↓
发布 UserRegisteredEvent

监听器:

发欢迎邮件

记录统计

一百六十九、事件的价值

让主业务:

减少直接依赖

一百七十、同步事件

默认很多事件监听:

同步执行

也就是说:

监听器慢
会拖慢主线程

一百七十一、异步事件

可以结合:

@Async

但要注意:

线程切换

事务边界

一百七十二、ApplicationRunner

SpringBoot 启动完成后可以:

@Component
public class InitRunner
        implements ApplicationRunner {

    @Override
    public void run(
            ApplicationArguments args
    ) {

        System.out.println(
                "应用启动完成"
        );
    }
}

一百七十三、CommandLineRunner

类似:

@Component
public class InitRunner
        implements CommandLineRunner {

    @Override
    public void run(
            String... args
    ) {

    }
}

一百七十四、Runner 适合什么

例如:

启动初始化

预热缓存

检查配置

但不要:

执行很慢导致应用迟迟不 ready

一百七十五、SpringBoot 启动事件

启动过程中有:

多种 ApplicationEvent

例如:

starting

environment prepared

context prepared

started

ready

具体事件类随版本体系存在。

当前:

理解生命周期思想

即可。


一百七十六、为什么启动失败会打印 Failure Analysis

SpringBoot 对部分常见启动异常:

做了 FailureAnalyzer

帮助输出:

Description

Action

比纯堆栈更友好。


一百七十七、配置元数据和 IDE 提示

Starter / ConfigurationProperties 可以提供:

配置元数据

IDEA 就能:

自动补全 application.yml 属性

一百七十八、SpringBoot DevTools

开发阶段可以使用:

spring-boot-devtools

提供:

自动重启
开发便利功能

生产环境:

通常不使用

一百七十九、Actuator 简单预览

SpringBoot Actuator:

应用监控和管理端点

例如:

health

metrics

info

企业部署很常见。


一百八十、为什么底层原理和 Actuator 有关系

SpringBoot 自动配置不仅负责:

业务基础设施

还可以按 Starter:

自动装配管理能力

一百八十一、自己写一个最小 Starter 的思路

以后可以:

my-sms-spring-boot-starter

里面:

1. SmsProperties

2. SmsService

3. SmsAutoConfiguration

4. AutoConfiguration.imports

使用者只需:

<dependency>
    ...
</dependency>

和:

sms:
  app-key: xxx

一百八十二、自定义 Starter 的价值

把公司内部公共功能:

短信

OSS

日志

鉴权 SDK

第三方支付

封装成:

一键依赖

一百八十三、SpringBoot 的“开箱即用”到底来自哪里

不是一个点。

而是组合:

Starter
+
AutoConfiguration
+
Conditional
+
ConfigurationProperties
+
Embedded Server
+
Spring IOC

一百八十四、底层原理总流程

pom.xml Starter
↓
依赖进入 classpath
↓
main()
↓
SpringApplication.run()
↓
Environment
↓
ApplicationContext
↓
解析 @SpringBootApplication
↓
ComponentScan
↓
EnableAutoConfiguration
↓
条件装配
↓
注册 BeanDefinition
↓
BeanFactoryPostProcessor
↓
BeanPostProcessor
↓
创建 Bean
↓
依赖注入
↓
AOP Proxy
↓
启动内嵌 Tomcat
↓
注册 DispatcherServlet
↓
映射 Controller
↓
Application Ready

一百八十五、SpringBoot 启动失败排查

遇到:

Application failed to start

按顺序看:

最底部 Caused by

Description

Action

端口

Bean 冲突

循环依赖

配置绑定

DataSource

Mapper

一百八十六、常见错误 1:Bean 找不到

例如:

NoSuchBeanDefinitionException

检查:

类有没有组件注解

包有没有被扫描

Bean 条件是否满足

配置类有没有加载

一百八十七、常见错误 2:同类型多个 Bean

NoUniqueBeanDefinitionException

解决:

@Primary

@Qualifier

重构 Bean

一百八十八、常见错误 3:Bean 名冲突

两个 Bean:

相同名字

可能导致:

冲突

现代 SpringBoot 默认通常:

不允许随意覆盖

一百八十九、常见错误 4:Controller 404

如果应用正常启动但:

接口 404

检查:

Controller 是否被扫描

@RequestMapping

context path

HTTP Method

启动类包位置

一百九十、常见错误 5:循环依赖

错误信息可能:

The dependencies of some of the beans
form a cycle

不要第一反应:

开启循环依赖

优先:

重构依赖

一百九十一、常见错误 6:端口占用

Web server failed to start.
Port 8080 was already in use.

Windows:

netstat -ano | findstr :8080

一百九十二、常见错误 7:DataSource 配置失败

例如:

Failed to configure a DataSource

检查:

JDBC Driver

url

username

password

数据库是否启动

一百九十三、常见错误 8:配置属性绑定失败

例如:

timeout: abc

Java:

Integer timeout;

无法转换:

启动失败

一百九十四、常见错误 9:自动配置没生效

例如加了配置:

却没有 Bean

检查:

starter

classpath

conditional

property

用户自定义 Bean

一百九十五、常见错误 10:事务没生效

现在从 Bean 底层可以解释:

Service 是否 Spring Bean

是否创建 Proxy

调用是否经过 Proxy

self-invocation

一百九十六、常见错误 11:AOP 不生效

同样检查:

目标对象是否 Bean

切点是否匹配

调用是否经过 Proxy

private/final/self-invocation

一百九十七、SpringBoot 底层和反射的关系

Spring 大量依赖:

反射

完成:

扫描类

读取注解

调用构造器

访问方法

参数绑定

Bean 创建

一百九十八、SpringBoot 底层和注解的关系

例如:

@Component

@Service

@Configuration

@Bean

@RequestMapping

@Transactional

本质:

元数据

框架运行时:

读取这些元数据

决定行为。


一百九十九、SpringBoot 底层和动态代理的关系

@Transactional

@Async

@Cacheable

AOP

大量依赖:

Proxy

二百、SpringBoot 底层和 JavaBean 的关系

配置绑定:

ConfigurationProperties

请求参数绑定:

DTO

都离不开:

JavaBean 属性模型

二百零一、SpringBoot 底层和 Maven 的关系

Starter:

本质是 Maven 依赖

依赖进入:

classpath

自动配置:

再根据 classpath 判断

所以:

Maven → classpath → AutoConfiguration

是连起来的。


二百零二、这就是下一章为什么是 Maven 高级

下一篇 Maven 高级会进一步解释:

依赖冲突

依赖仲裁

dependencyManagement

多模块

插件

生命周期

父子工程

私服

这些都直接影响:

SpringBoot 依赖和自动配置

二百零三、练习 1:获取 Bean

启动后:

ApplicationContext context =
        SpringApplication.run(
                DemoApplication.class,
                args
        );

System.out.println(
        context.getBean(
                UserService.class
        )
);

观察:

Bean 是否存在

二百零四、练习 2:自己定义 Bean

@Configuration
public class AppConfig {

    @Bean
    public Random random() {

        return new Random();
    }
}

注入:

private final Random random;

二百零五、练习 3:包扫描

故意把 Controller:

移动到启动类兄弟根包之外

观察:

404

再移动回来。


二百零六、练习 4:两个同类型 Bean

定义:

AliPayService

WechatPayService

都实现:

PaymentService

注入接口,

观察:

NoUniqueBeanDefinitionException

再用:

@Primary / @Qualifier

解决。


二百零七、练习 5:Bean 生命周期

@PostConstruct
public void init() {

    System.out.println(
            "init"
    );
}

@PreDestroy
public void destroy() {

    System.out.println(
            "destroy"
    );
}

观察:

启动和关闭日志

二百零八、练习 6:ConditionalOnProperty 思想

自己写:

feature.demo.enabled

值为:

true

才创建 Bean。


二百零九、练习 7:ConfigurationProperties

application.yml:

app:
  name: rbac-system
  timeout: 5000

绑定:

AppProperties

二百一十、练习 8:debug 自动配置

配置:

debug=true

启动项目。

找到:

CONDITIONS EVALUATION REPORT

观察:

Positive matches

Negative matches

二百一十一、练习 9:查看 AOP 代理类

在 Controller 打印:

System.out.println(
        userService.getClass()
);

如果 UserService 有:

@Transactional / AOP

观察实际 class。


二百一十二、练习 10:self-invocation

再次做:

this.transactionMethod()

结合:

Proxy

解释为什么事务不生效。


二百一十三、练习 11:内嵌 Tomcat

运行 SpringBoot:

不启动外部 Tomcat

观察日志:

Tomcat started on port ...

理解:

Tomcat 已嵌入项目

二百一十四、练习 12:修改端口

application.yml:

server:
  port: 8081

观察:

Environment 配置
如何影响自动配置 Bean

二百一十五、练习 13:Runner

创建:

ApplicationRunner

启动时打印:

初始化完成

二百一十六、必须掌握 IOC

IOC

ApplicationContext

Bean

BeanDefinition

BeanFactory

二百一十七、必须掌握 DI

Constructor Injection

@Autowired

@Primary

@Qualifier

二百一十八、必须掌握组件扫描

@Component

@Service

@Repository

@Controller

@RestController

@Configuration

@ComponentScan

二百一十九、必须掌握 SpringBoot 核心

@SpringBootApplication

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

二百二十、必须掌握自动配置

AutoConfiguration

Conditional

ConditionalOnClass

ConditionalOnMissingBean

ConditionalOnProperty

二百二十一、必须掌握 Starter

Starter
负责依赖组合

AutoConfiguration
负责配置 Bean

二百二十二、必须掌握 Bean 生命周期

实例化

依赖注入

Aware

BeanPostProcessor

初始化

AOP Proxy

销毁

二百二十三、必须掌握启动流程

SpringApplication.run

Environment

ApplicationContext

BeanDefinition

refresh

BeanPostProcessor

Embedded Tomcat

DispatcherServlet

二百二十四、必须掌握 Spring MVC 底层链路

Tomcat

DispatcherServlet

HandlerMapping

HandlerMethod

HandlerAdapter

ArgumentResolver

HttpMessageConverter

Controller

二百二十五、面试题 1

问:

@SpringBootApplication 是什么?

答:

它是 SpringBoot 主启动类常用的组合注解。

核心可以理解为组合了:
@SpringBootConfiguration、
@EnableAutoConfiguration、
@ComponentScan。

分别负责配置类语义、自动配置和组件扫描。

二百二十六、面试题 2

问:

Spring IOC 是什么?

答:

IOC 是控制反转。

传统代码由程序员自己 new 对象,
Spring 中对象的创建、依赖关系和生命周期
交给 IOC 容器管理。

Spring 管理的对象称为 Bean。

二百二十七、面试题 3

问:

IOC 和 DI 有什么区别?

答:

IOC 是整体思想:
对象控制权交给容器。

DI 是实现 IOC 的核心方式:
容器把一个 Bean 所需要的依赖注入进去。

二百二十八、面试题 4

问:

为什么推荐构造器注入?

答:

构造器注入可以让依赖更加明确,
字段可以声明为 final,
方便单元测试,
并保证对象创建完成后依赖已经完整。

二百二十九、面试题 5

问:

SpringBoot 自动配置原理是什么?

答:

SpringBoot 会加载一批自动配置候选类,
然后根据 classpath、已有 Bean、
配置属性、应用类型等条件进行判断。

满足条件的自动配置才会注册 Bean。

因此自动配置本质仍然是
@Configuration/@Bean + 条件装配。

二百三十、面试题 6

问:

Starter 和 AutoConfiguration 有什么区别?

答:

Starter 主要解决依赖组合问题,
把一组相关 jar 一次性引入。

AutoConfiguration 主要解决 Bean 配置问题,
根据条件自动创建框架需要的 Bean。

两者共同构成 SpringBoot 的开箱即用体验。

二百三十一、面试题 7

问:

@ConditionalOnMissingBean 有什么作用?

答:

只有当 Spring 容器中不存在指定 Bean 时,
自动配置才创建默认 Bean。

这使得 SpringBoot 可以提供默认配置,
同时允许用户自己定义 Bean 覆盖默认行为。

二百三十二、面试题 8

问:

为什么启动类推荐放根包?

答:

@SpringBootApplication 默认会从启动类所在包
向下扫描子包。

如果 Controller 或 Service 放在扫描范围外,
它们不会注册为 Bean,
可能导致注入失败或接口 404。

二百三十三、面试题 9

问:

Spring Bean 默认是单例吗?

答:

默认是 singleton。

同一个 ApplicationContext 中,
通常只有一个对应 Bean 实例。

因此 Controller 和 Service 不应该用普通成员变量
保存某个请求的临时状态。

二百三十四、面试题 10

问:

BeanPostProcessor 是什么?

答:

BeanPostProcessor 是 Bean 后置处理扩展点。

Spring 可以在 Bean 初始化前后统一处理对象。

Spring AOP 代理的创建就和 Bean 后置处理机制
有非常密切的关系。

二百三十五、面试题 11

问:

SpringApplication.run() 做了什么?

答:

它会准备 Environment,
创建 ApplicationContext,
加载和解析配置类,
注册 BeanDefinition,
刷新 Spring 容器,
创建 Bean,
执行自动配置,
启动内嵌 Web Server,
并最终完成应用启动。

二百三十六、面试题 12

问:

SpringBoot 为什么能直接启动 Tomcat?

答:

spring-boot-starter-web 会引入 Web 和内嵌 Tomcat
所需的相关依赖。

SpringBoot 自动配置检测到 Servlet Web 环境后,
会创建内嵌 WebServerFactory,
进而启动 Tomcat。

因此可以直接运行 main 方法启动 Web 应用。

二百三十七、面试题 13

问:

DispatcherServlet 是什么?

答:

DispatcherServlet 是 Spring MVC 的前端控制器。

大部分 MVC 请求先进入 DispatcherServlet,
再通过 HandlerMapping 找到 Controller 方法,
通过 HandlerAdapter 调用,
最后完成参数解析和响应转换。

二百三十八、面试题 14

问:

@RequestBody 为什么能自动把 JSON 转成对象?

答:

Spring MVC 会通过参数解析器识别 @RequestBody,
然后选择合适的 HttpMessageConverter。

在常见 SpringBoot Web 项目中,
Jackson MessageConverter 会把 JSON
反序列化成 Java 对象。

二百三十九、面试题 15

问:

为什么 @Transactional 自调用会失效?

答:

因为事务通常依赖 Spring AOP 代理。

外部调用经过 Proxy,
事务拦截器才有机会执行。

this.method() 是目标对象内部直接调用,
绕过 Proxy,
因此事务增强可能不会执行。

二百四十、SpringBoot 底层知识树

SpringBoot
│
├─ Spring Core
│  ├─ IOC
│  ├─ DI
│  ├─ BeanFactory
│  ├─ ApplicationContext
│  ├─ BeanDefinition
│  └─ Bean Lifecycle
│
├─ Component
│  ├─ Component
│  ├─ Service
│  ├─ Repository
│  ├─ Controller
│  ├─ Configuration
│  └─ Bean
│
├─ Boot
│  ├─ SpringBootApplication
│  ├─ EnableAutoConfiguration
│  ├─ ComponentScan
│  ├─ Starter
│  ├─ AutoConfiguration
│  └─ Conditional
│
├─ Config
│  ├─ Environment
│  ├─ application.yml
│  ├─ Value
│  └─ ConfigurationProperties
│
├─ Extension
│  ├─ BeanFactoryPostProcessor
│  ├─ BeanPostProcessor
│  ├─ AOP Proxy
│  └─ Events
│
├─ Startup
│  ├─ SpringApplication
│  ├─ Environment
│  ├─ Context
│  ├─ refresh
│  ├─ Bean Init
│  └─ Runner
│
└─ Web
   ├─ Embedded Tomcat
   ├─ DispatcherServlet
   ├─ HandlerMapping
   ├─ HandlerAdapter
   ├─ ArgumentResolver
   └─ HttpMessageConverter

二百四十一、最重要的启动总图

main()
↓
SpringApplication.run()
↓
准备 Environment
↓
创建 ApplicationContext
↓
加载 @SpringBootApplication
↓
ComponentScan
↓
EnableAutoConfiguration
↓
读取 AutoConfiguration
↓
Conditional 判断
↓
注册 BeanDefinition
↓
BeanFactoryPostProcessor
↓
BeanPostProcessor
↓
创建 Bean
↓
依赖注入
↓
初始化
↓
AOP Proxy
↓
启动内嵌 Tomcat
↓
创建/注册 DispatcherServlet
↓
扫描 RequestMapping
↓
运行 Runner
↓
Application Ready

二百四十二、本章总结

SpringBoot 底层最核心不是:

背源码类名

而是理解:

SpringBoot 如何把大量配置自动化

IOC:

对象交给容器管理

DI:

容器自动注入依赖

Bean:

Spring 管理的对象

@SpringBootApplication:

主配置
+
组件扫描
+
自动配置

自动配置:

AutoConfiguration
+
Conditional

Starter:

负责快速引入一组依赖

Bean 生命周期:

BeanDefinition
↓
实例化
↓
依赖注入
↓
初始化
↓
BeanPostProcessor
↓
AOP Proxy
↓
使用
↓
销毁

SpringBoot 启动:

SpringApplication.run()
↓
Environment
↓
ApplicationContext
↓
refresh
↓
Bean
↓
Embedded Tomcat
↓
DispatcherServlet

Spring MVC 请求:

Tomcat
↓
DispatcherServlet
↓
HandlerMapping
↓
HandlerAdapter
↓
ArgumentResolver
↓
Controller
↓
HttpMessageConverter
↓
JSON

现在再回头看这些注解:

@Service

@RestController

@Transactional

@RequestBody

就不再是:

“SpringBoot 很神奇,所以自动能用”

而是能够解释:

组件扫描注册 Bean

IOC 管理 Bean

DI 注入 Bean

BeanPostProcessor 创建 Proxy

自动配置提供框架 Bean

DispatcherServlet 负责 Web 请求

到这里,你对 SpringBoot 已经从:

会用

进入:

能解释基本底层机制

按照课程表,下一篇继续:

Maven 高级

会进一步系统学习:

依赖传递

依赖冲突

依赖仲裁

dependencyManagement

父子工程

多模块

生命周期

插件

profile

私服

SpringBoot Maven 构建