一、第一阶段:认识 Spring Boot

文章目录

Spring Boot 可以分成下面四层学习:

层级 核心目标 学习结果

第一层:会使用 会创建、配置、启动 Spring Boot 项目 能独立开发普通接口

第二层:懂机制 理解启动、自动配置、条件装配 遇到自动配置问题能自己排查

第三层:会生产化 掌握日志、测试、Actuator、优雅停机 应用能稳定部署和运行

第四层:会扩展 自定义自动配置、Starter、源码调试 能封装公司级公共组件

推荐学习顺序:

1、认识 Spring Boot

一、Spring Boot 是什么?

一句话理解:Spring Boot 是一套帮助我们快速创建、配置、启动和运行 Spring 应用的开发框架。

官方对 Spring Boot 的定位是:让开发者能够轻松创建可以直接运行、适合生产环境的 Spring 应用。

注意:Spring Boot ≠ 完全独立的新框架,Spring Boot = 对 Spring 应用开发过程的自动化和简化。它没有抛弃 Spring,而是把 Spring 应用中大量重复、烦琐的配置工作自动完成。

二、Spring Boot 主要解决什么问题?

在没有 Spring Boot 的情况下,创建一个 Spring 应用通常需要手动处理很多事情:

text 复制代码
选择各种组件
        ↓
添加对应依赖
        ↓
配置组件
        ↓
配置运行环境
        ↓
配置服务器
        ↓
部署应用
        ↓
启动应用

Spring Boot 将这个过程简化为:

text 复制代码
选择需要的功能
        ↓
编写少量配置
        ↓
运行 main 方法
        ↓
应用启动

Spring Boot 最主要解决以下几个问题。

问题 Spring Boot 的解决方式
配置内容太多 自动配置
不知道应该引入哪些组件 Starter 启动器
组件版本容易冲突 统一管理组件版本
需要单独安装服务器 提供内嵌服务器
部署流程烦琐 可以打包成可执行程序
不同环境配置麻烦 外部化配置
应用运行状态难以查看 Actuator 监控能力

三、Spring Boot 的核心思想

学习 Spring Boot,首先要理解四个核心思想。

1. 约定优于配置

它的核心思想是:框架提前规定一套常用、合理的默认规则。开发者遵守这些规则时,不需要额外写配置;只有不想使用默认规则时,才需要显式配置。也可以简单理解为:按照框架默认的方式做,框架自动帮你处理;偏离默认方式,才需要自己配置。

注意,它并不是:完全不需要配置。而是:减少重复、机械、没有必要的配置。

Spring Boot 为大量配置提供了默认值。

例如,默认配置文件名称:application.properties、application.yml、application.yaml只要按照 Spring Boot 的约定组织项目,它就能够自动识别并完成很多配置。这意味着:

text 复制代码
没有特殊需求
    ↓
直接使用默认配置

有特殊需求
    ↓
覆盖默认配置

所以 Spring Boot 不是"不需要配置",而是常规情况使用默认配置,特殊情况再自定义配置。

约定与配置的关系

二者并不是互相排斥的。正确关系是:

text 复制代码
约定提供默认行为
配置覆盖默认行为

执行逻辑可以理解为:

text 复制代码
用户有没有明确配置?
        │
        ├── 有 → 使用用户配置
        │
        └── 没有 → 使用框架约定

因此,"约定优于配置"的"优于"不是说:配置没有用。而是说:在能使用合理默认值的情况下,优先通过统一约定减少配置。

约定优于配置不等于"猜测"

框架并不是随便猜测你的想法,而是按照事先确定的规则工作。例如:

text 复制代码
banner.txt 是 Banner
application.yml 是主配置文件
static 目录用于静态资源
启动类所在包及其子包是默认扫描范围
8080 是默认服务器端口

这些都是明确的规则。所以更准确地说:约定就是框架提前写好的默认配置规则。你没有亲自写配置,不代表没有配置。实际上,很多配置已经存在于框架内部,只是框架替你提供了默认值。

如果没有"约定优于配置"会怎样?

假设我们开发一个最简单的 Web 项目。如果框架没有默认约定,可能需要手动配置:

text 复制代码
服务器使用什么?
服务器端口是多少?
启动服务器的类在哪里?
Controller 放在哪个包?
静态资源放在哪里?
配置文件叫什么?
日志框架使用什么?
JSON 使用什么组件转换?
错误页面放在哪里?
数据库连接池使用哪一种?

即使所有项目的答案都差不多,也要每次重新写一遍。例如:

xml 复制代码
<server>
    <type>tomcat</type>
    <port>8080</port>
</server>

<json>
    <converter>jackson</converter>
</json>

<static-resource>
    <path>classpath:/static/</path>
</static-resource>

这些配置大多数项目都一样,却必须反复编写。Spring Boot 则提前约定:

  • 默认使用嵌入式 Tomcat
  • 默认端口是 8080
  • 默认使用 Jackson 处理 JSON
  • 默认配置文件叫 application.properties 或 application.yml
  • 默认静态资源放在 static、public 等目录
  • 默认扫描启动类所在包及其子包

因此,开发者通常不需要配置这些内容。

约定大于配置在Spring Boot中的具体体现

Spring Boot 中最典型的例子:默认端口

创建一个 Spring Boot Web 项目,不配置端口,应用默认启动在:8080端口,你不需要写:

yml 复制代码
server:
  port: 8080

因为 Spring Boot 已经约定:Web 服务器默认端口是 8080。

遵守约定: 配置文件中什么都不写,不配置 server.port,启动后使用:http://localhost:8080访问。

覆盖约定: 假如你不想使用 8080,就显式配置:

yml 复制代码
server:
  port: 9090

启动地址就变成:http://localhost:9090

这个例子非常完整地体现了约定优于配置:

场景 是否需要配置
使用默认端口 8080 不需要
使用自定义端口 9090 需要

自动配置中的约定优于配置

Spring Boot 最核心的能力之一是:自动配置,它会根据以下信息判断应该配置什么:

  • 你引入了哪些依赖
  • 类路径下有哪些类
  • 你有没有自己创建某些 Bean
  • 配置文件里写了什么
  • 当前是什么类型的应用

例如,引入 Web 启动器:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot 看到相关依赖后,会按照默认约定准备:

text 复制代码
Spring MVC
DispatcherServlet
Jackson
嵌入式 Tomcat
参数转换器
异常处理基础设施
静态资源处理器

所以你只需要写:

java 复制代码
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

启动后就能访问:http://localhost:8080/hello

你没有手动创建 Tomcat,也没有手动配置 DispatcherServlet,但项目仍然可以运行。

配置文件名称的约定

Spring Boot 默认会读取:application.properties或者:application.yml。通常放在:src/main/resources/,完整路径例如:src/main/resources/application.yml

遵守约定: 创建src/main/resources/application.yml,写入:

yml 复制代码
server:
  port: 9090

Spring Boot 会自动读取。你不需要告诉它:配置文件在 resources 目录、配置文件名叫 application.yml。因为这些已经是默认约定。

不遵守约定: 假设你把配置文件命名为:my-config.yml,Spring Boot 默认不会把它当作主配置文件加载。这时必须额外告诉 Spring Boot,请读取 my-config.yml。这就是:遵守约定,不需要配置;改变约定,需要额外配置。

启动类位置的约定

Spring Boot 默认会扫描:启动类所在的包,以及它下面的所有子包。例如项目结构:

text 复制代码
com.example.demo
├── DemoApplication.java
├── controller
│   └── UserController.java
├── service
│   └── UserService.java
└── repository
    └── UserRepository.java

启动类:

java 复制代码
package com.example.demo;

@SpringBootApplication
public class DemoApplication {

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

这里的启动类位于:com.example.demo,Spring Boot 默认扫描:

text 复制代码
com.example.demo
com.example.demo.controller
com.example.demo.service
com.example.demo.repository

所以这些类通常都能被自动发现:

java 复制代码
@RestController
public class UserController {
}
@Service
public class UserService {
}
@Repository
public class UserRepository {
}

你不需要手动配置:

java 复制代码
@ComponentScan({
    "com.example.demo.controller",
    "com.example.demo.service",
    "com.example.demo.repository"
})

启动类放错位置会怎样?假设项目结构变成:

text 复制代码
com.example.app
└── DemoApplication.java

com.example.controller
└── UserController.java

启动类所在包是:com.example.app,默认扫描范围是:com.example.app,但是 UserController 位于:com.example.controller,它不是 com.example.app 的子包,因此可能扫描不到。这时有两个解决办法。

方法一:遵守约定,调整包结构,将启动类放在共同的父包:

text 复制代码
com.example
├── DemoApplication.java
├── controller
│   └── UserController.java
└── service
    └── UserService.java

这是更推荐的做法。

方法二:显式配置扫描路径

java 复制代码
@SpringBootApplication
@ComponentScan("com.example")
public class DemoApplication {
}

这里再次体现:包结构遵守 Spring Boot 的约定,就不需要配置扫描路径;包结构不遵守约定,就需要自己指定。

静态资源目录的约定

Spring Boot 默认会从以下位置寻找静态资源:

text 复制代码
classpath:/static/
classpath:/public/
classpath:/resources/
classpath:/META-INF/resources/

最常见的是:

text 复制代码
src/main/resources/static/

例如:

text 复制代码
src/main/resources/static/logo.png

启动项目后,可以访问:http://localhost:8080/logo.png,不需要写 Controller:

java 复制代码
@GetMapping("/logo.png")
public byte[] getLogo() {
    // 不需要这样做
}

也不需要手动配置静态资源映射,因为 Spring Boot 已经有默认约定。自定义静态资源路径,假如你非要把静态文件放到:src/main/resources/my-files/。那就需要显式配置资源路径,或者自行实现资源映射。因此:static 目录,不是 Java 语法要求,而是 Spring Boot 的目录约定。

自定义 Banner 也是约定优于配置

Spring Boot 的自定义 Banner。Spring Boot 约定启动时自动查找:src/main/resources/banner.txt,只要创建这个文件:src/main/resources/banner.txt,然后写入:

text 复制代码
==================================
       用户管理系统启动中
==================================

启动时就会自动显示。你不需要编写:

java 复制代码
File bannerFile = new File("banner.txt");
读取文件......
输出文件......

也不需要告诉 Spring Boot:这是 Banner 文件,请在启动时加载它。因为 Spring Boot 已经约定:resources 目录下名为 banner.txt 的文件,就是启动 Banner。如果文件叫:my-banner.txt那就偏离了默认约定,需要显式配置:

yml 复制代码
spring:
  banner:
    location: classpath:my-banner.txt

对比一下:

文件名 是否需要额外配置
banner.txt 不需要
my-banner.txt 需要

这就是非常典型的"约定优于配置"。

JSON 转换的约定

假设 Controller 返回一个 Java 对象:

java 复制代码
@RestController
public class UserController {

    @GetMapping("/user")
    public User getUser() {
        User user = new User();
        user.setId(1L);
        user.setName("张三");
        return user;
    }
}

对象:

java 复制代码
public class User {

    private Long id;
    private String name;

    // getter、setter
}

访问接口时,浏览器收到:

json 复制代码
{
  "id": 1,
  "name": "张三"
}

为什么 Java 对象自动变成了 JSON?因为 Spring Boot Web 项目默认约定:

  • 接口返回对象时,使用消息转换器处理
  • 类路径中存在 Jackson 时,默认使用 Jackson
  • @RestController 的返回值默认写入响应体

你不需要每个接口都写:

java 复制代码
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user);
return json;

修改默认行为, 假如日期默认输出不符合要求,可以配置:

yml 复制代码
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: Asia/Shanghai

也就是说:

  • 普通 JSON 转换使用默认约定
  • 特殊格式再进行配置

嵌入式服务器的约定

以前部署 Java Web 项目,通常需要:

text 复制代码
安装 Tomcat
配置 Tomcat
将项目打成 WAR
把 WAR 放入 Tomcat 的 webapps
启动 Tomcat

Spring Boot Web 项目通常直接内置 Tomcat。只要启动:

java 复制代码
SpringApplication.run(DemoApplication.class, args);

Tomcat 就会跟着启动。这是因为 Spring Boot 约定:引入 Web 启动器后,如果没有其他特殊设置,就创建嵌入式 Web 服务器。你不需要手动写:

java 复制代码
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.start();

当然,你也可以更换服务器,例如排除 Tomcat,改用 Jetty。但这是特殊需求,所以需要额外配置依赖。

Bean 名称也有约定

例如:

java 复制代码
@Service
public class UserService {
}

默认 Bean 名称通常是:userService。也就是:

  • 类名:UserService
  • 默认 Bean 名:userService

你可以这样获取:

java 复制代码
Object bean = applicationContext.getBean("userService");

如果你想指定其他名称:

java 复制代码
@Service("myUserService")
public class UserService {
}

这时 Bean 名称变成:myUserService

因此:

写法 Bean 名
@Service 默认按照类名生成
@Service("myUserService") 使用指定名称

这同样是:有默认规则时不需要写;想改变默认规则时再配置。

2. 自动配置

自动配置是 Spring Boot 最核心的能力之一。

Spring Boot 的自动配置核心含义是: Spring Boot 在项目启动时,根据当前项目中已有的依赖、配置项、Bean、运行环境等条件,判断项目需要哪些基础组件,并自动把这些组件创建成 Bean 放入 Spring 容器。

Spring Boot 会根据当前应用中存在的组件、配置项和 Bean,判断应该配置哪些功能。官方文档将其描述为:根据应用中已经加入的依赖,尝试自动配置 Spring 应用。可以把它理解成:

text 复制代码
Spring Boot 查看当前应用具备哪些条件
                    ↓
判断应该启用哪些配置
                    ↓
自动创建应用需要的对象

例如,当 Spring Boot 发现应用具备某项 Web 功能所需要的条件时,就会自动准备相应的 Web 运行环境。自动配置不是强制配置。例如,你引入:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot 发现项目中出现了:

text 复制代码
Spring MVC
Servlet API
Tomcat
Jackson

它便推测:这是一个基于 Servlet 的 Web 项目。于是自动配置:

text 复制代码
嵌入式 Tomcat
Spring MVC
DispatcherServlet
JSON 消息转换器
静态资源处理器
错误页面处理
参数绑定和类型转换等组件

Spring Boot 官方对自动配置的描述也是:根据加入项目的 jar 依赖,尝试自动配置 Spring 应用;例如引入 Web Starter 后,由于类路径中出现了 Tomcat 和 Spring MVC,Spring Boot 会据此配置 Web 应用。

先理解:自动配置到底"自动"了什么

自动配置最主要的工作是:自动向 Spring 容器中注册一些基础 Bean。 例如以前可能需要你手动写:

java 复制代码
@Configuration
public class WebConfig {

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
}

在 Spring Boot 中,很多这类通用 Bean 不需要你自己创建。你只需要:

yml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456

Spring Boot 就会读取这些配置,自动创建对应的 DataSource Bean。因此,"自动配置"并不是自动写你的业务代码,也不是自动生成 Controller、Service,而是:自动完成框架基础设施的配置和 Bean 注册。

Spring Boot 的自动配置通常遵循一个重要原则:

text 复制代码
开发者没有自己配置
        ↓
Spring Boot 使用默认配置

开发者已经自己配置
        ↓
Spring Boot 退让,使用开发者配置

这通常被称为:用户配置优先,自动配置退让。

自动配置的入口:@SpringBootApplication

一个标准的 Spring Boot 启动类通常是:

java 复制代码
@SpringBootApplication
public class DemoApplication {

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

其中最关键的是:@SpringBootApplication,它本质上组合了三个重要注解:

text 复制代码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

也可以近似理解成:

java 复制代码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class DemoApplication {
}

Spring Boot 2.7 官方文档说明,@SpringBootApplication 组合了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan。其中真正开启自动配置机制的是 @EnableAutoConfiguration。

注解 作用
@SpringBootConfiguration 表示这是 Spring Boot 的主要配置类
@EnableAutoConfiguration 开启 Spring Boot 自动配置
@ComponentScan 扫描项目中自己编写的组件

需要特别区分:@ComponentScan负责扫描你自己写的:

text 复制代码
@Controller
@Service
@Repository
@Component

而:@EnableAutoConfiguration负责加载 Spring Boot 提供的自动配置类。

自动配置的整体运行流程

可以把自动配置过程理解成下面几步。

text 复制代码
启动 Spring Boot
      ↓
读取 @SpringBootApplication
      ↓
发现 @EnableAutoConfiguration
      ↓
加载大量候选自动配置类
      ↓
检查每个自动配置类的生效条件
      ↓
条件满足:注册其中的 Bean
条件不满足:跳过
      ↓
完成 Spring 容器初始化

再展开一点:

text 复制代码
1. 找到候选自动配置类
2. 判断项目中有没有相关依赖
3. 判断用户有没有自己创建对应 Bean
4. 判断配置文件中的开关和参数
5. 判断是不是 Web 环境
6. 条件全部满足才执行配置

重点在于:Spring Boot 并不是把所有自动配置全部无条件执行,而是先加载候选配置,再通过条件进行筛选。

Spring Boot 本身提供了大量自动配置类,例如:

text 复制代码
DataSourceAutoConfiguration
WebMvcAutoConfiguration
JacksonAutoConfiguration
RabbitAutoConfiguration
RedisAutoConfiguration
MailSenderAutoConfiguration
TransactionAutoConfiguration

这些类主要位于:spring-boot-autoconfigure这个 jar 包中。

在 Spring Boot 2.7 中,自动配置候选类可以登记在下面这个文件中:

text 复制代码
META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件内容可以近似理解为:

java 复制代码
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration

Spring Boot 启动时读取这个名单,知道有哪些自动配置类可以尝试加载。官方文档说明,Spring Boot 2.7 会检查 jar 中的 AutoConfiguration.imports 文件,该文件按行列出候选自动配置类。但是:出现在候选名单中,不代表一定会生效。后面还要判断各种条件。

通过条件注解决定是否生效,自动配置真正的核心是:@Conditional,也就是:符合某些条件,配置才生效。常见的条件注解包括:

条件注解 含义
@ConditionalOnClass 类路径中存在指定类时生效
@ConditionalOnMissingClass 类路径中不存在指定类时生效
@ConditionalOnBean 容器中存在指定 Bean 时生效
@ConditionalOnMissingBean 容器中不存在指定 Bean 时生效
@ConditionalOnProperty 配置项满足条件时生效
@ConditionalOnWebApplication 当前是 Web 应用时生效
@ConditionalOnNotWebApplication 当前不是 Web 应用时生效
@ConditionalOnResource 指定资源文件存在时生效

Spring Boot 官方文档把条件分为类条件、Bean 条件、属性条件、资源条件、Web 应用条件等类别。自动配置类最常使用的就是 @ConditionalOnClass 和 @ConditionalOnMissingBean。

@ConditionalOnClass含义:项目中存在某个类,配置才生效。例如:

java 复制代码
@Configuration
@ConditionalOnClass(RabbitTemplate.class)
public class RabbitAutoConfiguration {
}

可以理解为:

text 复制代码
项目中存在 RabbitTemplate 类吗?
    │
    ├── 存在 → 说明项目引入了 RabbitMQ 相关依赖
    │          可以继续配置 RabbitMQ
    │
    └── 不存在 → 跳过 RabbitMQ 自动配置

为什么要这样做?因为一个普通 Web 项目可能根本不使用 RabbitMQ。如果没有这个条件,Spring Boot 每次启动都尝试创建 RabbitMQ 组件,就会产生大量错误。

@ConditionalOnMissingBean含义:Spring 容器中不存在指定类型的 Bean 时,才创建默认 Bean。例如下面是简化示意:

java 复制代码
@Configuration
public class SomeAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new DefaultSomeService();
    }
}

执行逻辑是:

text 复制代码
容器中有没有 SomeService?
    │
    ├── 没有 → Spring Boot 创建默认 SomeService
    │
    └── 有   → 使用用户自己的,不再创建默认对象

官方文档也强调,@ConditionalOnMissingBean 的重要作用,就是允许开发者用自己的 Bean 覆盖自动配置提供的默认实现。

@ConditionalOnProperty含义:根据配置文件中的配置项决定是否启用某个功能。例如:

java 复制代码
@Configuration
@ConditionalOnProperty(
    prefix = "my.feature",
    name = "enabled",
    havingValue = "true"
)
public class MyFeatureAutoConfiguration {
}

配置:

yml 复制代码
my:
  feature:
    enabled: true

这时自动配置生效。如果改成:

yml 复制代码
my:
  feature:
    enabled: false

自动配置不生效。执行逻辑:

text 复制代码
my.feature.enabled 是否等于 true?
    │
    ├── 是 → 加载配置
    └── 否 → 跳过配置

@ConditionalOnProperty 可以通过配置属性、期望值以及 matchIfMissing 等参数控制条件。

@ConditionalOnWebApplication含义:只有当前项目是 Web 项目时,配置才生效。例如:

java 复制代码
@Configuration
@ConditionalOnWebApplication
public class WebAutoConfiguration {
}

如果当前是:Spring MVC Web 应用就执行。如果只是一个:

text 复制代码
普通定时任务程序
批处理程序
命令行程序

则不会执行 Web 相关配置。

把自动配置写成一段伪代码,自动配置可以理解成下面的逻辑:

java 复制代码
if (项目中存在某个依赖) {

    if (当前环境符合要求) {

        if (用户没有自己创建对应的Bean) {

            根据application.yml中的配置 {
                创建默认Bean;
                放入Spring容器;
            }
        }
    }
}

数据源自动配置

引入:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

再引入 MySQL 驱动:

xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

配置:

yml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

你没有写:

java 复制代码
@Bean
public DataSource dataSource() {
    // 手动创建连接池
}

但是仍然可以注入:

java 复制代码
@Autowired
private DataSource dataSource;

这是因为 Spring Boot 自动创建了 DataSource Bean。

数据源自动配置大致过程:

text 复制代码
项目中存在 DataSource 类吗?
        │
        ├── 否 → 不进行数据源配置
        │
        └── 是
             ↓
容器中已经有 DataSource Bean 吗?
        │
        ├── 有 → 使用用户自己的
        │
        └── 没有
             ↓
读取 spring.datasource 配置
             ↓
选择连接池实现
             ↓
创建 DataSource Bean

在 Spring Boot 2.7 中,如果 HikariCP 存在,默认优先选择 HikariCP;而 spring-boot-starter-jdbc 和 spring-boot-starter-data-jpa 默认会带入 HikariCP。

自动配置通常需要同时满足:

text 复制代码
依赖存在
+
环境正确
+
配置合理
+
用户没有提供替代 Bean

这就是 Spring Boot 自动配置最核心的运行模式。

手写一个简单的"自动配置"

下面通过自己实现一个简化版功能,彻底理解自动配置。

假设有一个类:

java 复制代码
public class MessageService {

    public String send(String message) {
        return "发送消息:" + message;
    }
}

创建自动配置类:

java 复制代码
@Configuration
@ConditionalOnClass(MessageService.class)
public class MessageAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MessageService messageService() {
        return new MessageService();
    }
}

含义是:

text 复制代码
如果项目中存在 MessageService 类
并且 Spring 容器中没有 MessageService Bean
就自动创建一个 MessageService

没有用户配置,用户直接注入:

java 复制代码
@RestController
public class MessageController {

    private final MessageService messageService;

    public MessageController(MessageService messageService) {
        this.messageService = messageService;
    }
}

Spring Boot 自动配置会创建默认对象,因此可以正常注入。

用户自己提供 Bean

java 复制代码
@Configuration
public class MyConfig {

    @Bean
    public MessageService messageService() {
        return new CustomMessageService();
    }
}

此时容器中已经存在 MessageService。所以自动配置中的:@ConditionalOnMissingBean,不满足,默认对象不会创建。

加入配置开关

java 复制代码
@Configuration
@ConditionalOnClass(MessageService.class)
@ConditionalOnProperty(
    prefix = "message",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true
)
public class MessageAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MessageService messageService() {
        return new MessageService();
    }
}

默认不配置时:matchIfMissing = true,所以功能开启。关闭:

yml 复制代码
message:
  enabled: false

这时整个自动配置不生效。这就是一个完整的简化版自动配置:

text 复制代码
类存在
+
配置项允许
+
用户没有自己的 Bean
=
创建默认 Bean

3. Starter 启动器

在 Spring Boot 中,Starter 通常翻译为:启动器,但它并不是"负责启动 Spring Boot 的程序"。更准确地说:Starter 是为某一种开发场景准备好的依赖集合。 例如,你要开发 Web 接口,需要:

  • Spring MVC
  • Spring Web
  • JSON 转换
  • 嵌入式 Tomcat
  • 日志组件

你不需要逐个寻找并引入,只需要添加:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot 官方把 Starter 定义为一种方便的"依赖描述器":它为某个开发场景集中提供所需的 Spring 及相关技术依赖,开发者不必四处寻找并复制大量依赖配置。一句话记忆:Starter 就是 Spring Boot 提供的"依赖套餐"、"功能集合"。

为什么需要 Starter

假设没有 Starter,你想开发一个 Web 项目,可能需要自己添加很多依赖:

xml 复制代码
<dependencies>
    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>

    <!-- Spring MVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
    </dependency>

    <!-- Tomcat -->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
    </dependency>

    <!-- JSON -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

    <!-- 日志 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
    </dependency>
</dependencies>

这还没有完全列完。你还需要考虑:

text 复制代码
这些依赖的版本是否兼容?
有没有漏掉传递依赖?
Tomcat 和 Spring MVC 版本是否匹配?
Jackson 版本是否匹配?
日志组件是否冲突?

有了 Starter,只需要写:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

它会通过 Maven 的传递依赖,把 Web 开发常用依赖一起引入。所以 Starter 主要解决两个问题:

xml 复制代码
不用逐个寻找依赖
不用自己组合常用依赖
Starter 的本质是什么

从 Maven 的角度看,Starter 本质上就是:一个提前写好依赖关系的 Maven 模块。 它的核心内容通常不是大量 Java 代码,而是一个类似下面的 pom.xml:

xml 复制代码
<project>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
    </dependencies>
</project>

当然,这是为了理解而写的简化版本。所以当你引入:spring-boot-starter-web,真正发生的是:

text 复制代码
你的项目
   ↓
依赖 spring-boot-starter-web
   ↓
starter-web 又依赖其他模块
   ↓
Maven 继续下载这些模块
   ↓
这些 jar 全部进入项目 classpath

以 Spring Boot 2.7 为例,官方说明:

  • spring-boot-starter-web 用于 Spring MVC Web 和 REST 应用,默认使用嵌入式 Tomcat。
  • spring-boot-starter-jdbc 用于 JDBC,并默认搭配 HikariCP。
  • spring-boot-starter-test 用于测试,包含 JUnit Jupiter、Hamcrest、Mockito 等常用测试库。
什么是传递依赖

理解 Starter,必须理解 Maven 的传递依赖。假设:

text 复制代码
你的项目依赖 A
A 又依赖 B
B 又依赖 C

那么 Maven 会把:A、B、C都加入你的项目。这就是传递依赖。对应到 Starter:

text 复制代码
你的项目
    ↓
spring-boot-starter-web
    ↓
spring-web
spring-webmvc
spring-boot-starter-json
spring-boot-starter-tomcat
spring-boot-starter
    ↓
更多底层依赖

所以你只在 pom.xml 中写了一个 Starter,但 IDEA 的 Maven 依赖列表中会出现很多 jar。这不是 Spring Boot 偷偷改了 pom.xml,而是 Maven 根据依赖关系,自动解析出了所有传递依赖。

例子:spring-boot-starter-web

引入 Starter:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

这个 Starter 面向的场景是:

text 复制代码
开发 Spring MVC Web 项目
开发 RESTful 接口
启动嵌入式 Web 服务器
进行 JSON 数据转换

Spring Boot 2.7 官方文档说明,spring-boot-starter-web 用于构建基于 Spring MVC 的 Web 和 REST 应用,默认嵌入式容器是 Tomcat。

可以简化理解为它大致引入以下内容:

text 复制代码
spring-boot-starter-web
│
├── spring-boot-starter
│   ├── Spring Boot 核心组件
│   ├── 自动配置支持
│   ├── 默认日志支持
│   └── YAML 支持
│
├── spring-boot-starter-json
│   └── Jackson 等 JSON 处理组件
│
├── spring-boot-starter-tomcat
│   └── 嵌入式 Tomcat
│
├── spring-web
│   └── Spring 基础 Web 能力
│
└── spring-webmvc
    └── Spring MVC 能力

注意,这是为了学习而简化后的依赖树,不是完整依赖列表。

为什么引入后就能写接口?添加 Starter 后,可以直接写:

java 复制代码
@RestController
public class UserController {

    @GetMapping("/user")
    public User getUser() {
        User user = new User();
        user.setId(1L);
        user.setName("张三");
        return user;
    }
}

启动项目后访问:http://localhost:8080/user,得到:

json 复制代码
{
  "id": 1,
  "name": "张三"
}

在这个过程中,Starter 为项目准备了所需依赖:Spring MVC、Tomcat、Jackson、Spring Web、日志组件,随后 Spring Boot 自动配置根据这些依赖,自动配置:

text 复制代码
嵌入式 Tomcat
DispatcherServlet
Spring MVC
JSON 消息转换器
静态资源处理器

因此,这个过程要拆成两个部分理解:

  • Starter: 把相关依赖引进来
  • 自动配置: 根据这些依赖创建和配置组件

4. 开箱即用

开箱即用,英文是:Out of the Box,可以简单理解为:把东西拿出来后,不需要进行大量安装、组装和配置,就能够直接使用。

在 Spring Boot 中,"开箱即用"通常表示:引入某个 Starter,提供少量必要配置后,Spring Boot 就会自动准备好相关组件,开发者可以直接编写业务代码。

例如开发一个 Web 接口,只需要引入:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

然后写一个 Controller:

java 复制代码
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello Spring Boot";
    }
}

启动项目后访问:http://localhost:8080/hello,就能得到:Hello Spring Boot。你没有手动:

text 复制代码
安装并配置 Tomcat
创建 DispatcherServlet
配置 Spring MVC
配置请求映射处理器
配置字符串响应转换器
指定服务器默认端口

但项目仍然可以运行。这就是 Spring Boot 所说的:开箱即用。

Spring Boot 为什么能做到开箱即用

Spring Boot 的开箱即用,不是某一个单独功能带来的,而是下面几个机制共同实现的:

text 复制代码
Starter
+
自动配置
+
约定优于配置
+
内嵌服务器
+
依赖版本管理

完整过程可以理解为:

text 复制代码
引入 Starter
      ↓
获得某个场景需要的一组依赖
      ↓
Spring Boot 自动配置检查项目环境
      ↓
按照默认约定创建相关 Bean
      ↓
使用少量配置覆盖必要参数
      ↓
开发者直接编写业务代码
1、Starter:把需要的材料准备好

前面已经学习过 Starter。Starter 的作用是:为某种开发场景准备好常用依赖。例如:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

它会带来 Web 开发常用的依赖,例如:

text 复制代码
Spring Web
Spring MVC
Jackson
嵌入式 Tomcat
日志组件

不使用 Starter 时,你可能要一个个寻找和引入这些依赖。使用 Starter 后,只需要引入一个依赖套餐。因此:Starter 负责把材料准备好

2、自动配置:把材料自动组装起来

只有依赖还不够。假设你的项目中有 Tomcat 的 jar,但没有代码创建和启动 Tomcat,它仍然不能自动运行。Spring Boot 的自动配置会根据当前环境判断:

text 复制代码
项目中有没有 Tomcat?
项目中有没有 Spring MVC?
当前是不是 Web 项目?
用户有没有自定义服务器配置?
用户有没有自己创建相关 Bean?

条件满足后,Spring Boot 自动创建并配置相关组件。例如:

text 复制代码
Starter 引入 Tomcat
        ↓
自动配置发现 Tomcat 类存在
        ↓
创建嵌入式服务器工厂
        ↓
按照默认端口启动 Tomcat

因此:自动配置负责把材料组装起来

3、约定优于配置:提供合理默认值

Spring Boot 为常见场景提供默认值,例如:

text 复制代码
服务器默认端口:8080
配置文件默认名称:application.yml
Banner 默认文件名:banner.txt
静态资源默认目录:static
默认 Web 服务器:Tomcat

如果这些默认值符合需求,你不需要配置。例如不写:

yml 复制代码
server:
  port: 8080

Tomcat 仍然会使用 8080 端口。只有想修改时,才需要配置:

yml 复制代码
server:
  port: 9090

因此:约定优于配置负责减少需要填写的参数

4、内嵌服务器:不用单独安装 Tomcat

传统 Java Web 项目经常需要:

text 复制代码
安装 Tomcat
      ↓
配置 Tomcat
      ↓
项目打成 WAR
      ↓
把 WAR 放入 webapps
      ↓
启动 Tomcat

Spring Boot Web 项目通常使用嵌入式 Tomcat。项目打成 jar 后,可以直接运行:

linux 复制代码
java -jar demo.jar

Tomcat 会随着 Spring Boot 应用一起启动。所以你不需要:

text 复制代码
单独下载 Tomcat
单独配置 Tomcat
把项目复制到 webapps
手动维护外部 Tomcat

这也是 Spring Boot 开箱即用的重要组成部分。

5. 总结:四大核心之间的关系

可以先记住下面这个流程:

text 复制代码
Starter
负责把需要的依赖准备好
        ↓
约定优于配置
提供一套默认规则和默认值
        ↓
自动配置
根据依赖、环境、配置自动创建 Bean
        ↓
开箱即用
开发者写少量代码就能运行

也可以这样理解:

核心 主要解决的问题
Starter 我需要引入哪些依赖?
约定优于配置 如果用户没有配置,默认应该怎么做?
自动配置 谁来根据环境创建 Tomcat、Spring MVC 等对象?
开箱即用 能不能引入依赖后,写少量代码直接运行?

四、Spring Boot 项目的核心启动类

一个典型的 Spring Boot 启动类如下:

java 复制代码
@SpringBootApplication
public class Application {

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

这里有两个重要部分:

  • @SpringBootApplication
  • SpringApplication.run(...)

五、@SpringBootApplication 是什么?

@SpringBootApplication 是 Spring Boot 应用最核心的注解之一。

它不是单一功能注解,而是一个组合注解,主要组合了:

java 复制代码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

官方文档也给出了这一对应关系。它们分别代表:

注解 主要作用
@SpringBootConfiguration 表明当前类是 Spring Boot 配置类
@EnableAutoConfiguration 开启 Spring Boot 自动配置
@ComponentScan 扫描并注册应用中的组件

因此:@SpringBootApplication,可以大致理解为:

text 复制代码
这是一个 Spring Boot 应用
        +
开启自动配置
        +
扫描应用组件

六、为什么启动类通常放在根包?

假设项目结构如下:

text 复制代码
com.example
├── Application.java
├── controller
├── service
├── config
└── component

启动类放在:com.example.Application。Spring Boot 默认会从启动类所在包开始,扫描它所在的包以及所有子包。扫描范围可以理解为:

text 复制代码
com.example
    ├── controller      √
    ├── service         √
    ├── config          √
    └── component       √

如果某个类位于启动类包结构之外:org.demo.service。默认情况下,它可能不会被扫描到。所以一般建议:将启动类放在项目最外层的根包中。

七、SpringApplication.run() 做了什么?

java 复制代码
SpringApplication.run(Application.class, args);

这行代码不是简单地执行一个普通方法。它负责启动整个 Spring Boot 应用。可以先用简化流程理解:

text 复制代码
执行 main 方法
        ↓
创建 SpringApplication
        ↓
判断当前应用类型
        ↓
准备运行环境
        ↓
读取配置文件
        ↓
创建 Spring 容器
        ↓
执行自动配置
        ↓
扫描并注册组件
        ↓
创建应用中的对象
        ↓
启动内嵌运行环境
        ↓
执行启动完成后的扩展逻辑
        ↓
应用启动完成

官方文档将 SpringApplication 定义为:一种通过 main() 方法引导启动 Spring 应用的便捷方式。

现阶段不需要立刻研究每一步的源码,但要先记住:SpringApplication.run() 是 Spring Boot 应用的总启动入口。

八、Spring Boot 为什么可以直接运行?

传统 Web 应用经常需要:

text 复制代码
准备服务器
    ↓
把应用部署到服务器
    ↓
启动服务器

Spring Boot 可以使用内嵌服务器,把服务器作为应用的一部分运行。 于是启动过程变成:

text 复制代码
启动 Spring Boot 应用
        ↓
同时启动内嵌服务器
        ↓
应用开始接收请求

Spring Boot 官方文档说明,Web 应用可以使用内嵌 Tomcat、Jetty 或 Netty 创建自包含的 HTTP 服务器。

因此,一个 Spring Boot 应用通常可以直接通过启动类运行,而不需要先手动把应用部署到外部服务器。

九、Spring Boot 配置文件

Spring Boot 通常使用以下配置文件:

application.properties、application.yml、application.yaml例如application.yml:

yml 复制代码
server:
  port: 8081

spring:
  application:
    name: demo

配置文件的主要作用是:

text 复制代码
修改 Spring Boot 默认配置
配置应用自身参数
配置不同运行环境
控制某些自动配置行为

Spring Boot 支持外部化配置,即同一份应用代码可以在不同环境下使用不同配置;配置来源可以包括配置文件、环境变量和命令行参数等。

十、Spring Boot 的"默认值"和"自定义值"

假设 Spring Boot 默认端口是某个值。应用没有配置端口时:使用 Spring Boot 默认值,应用进行了配置时:

yml 复制代码
server:
  port: 8088

那么:用户配置覆盖默认配置,因此可以把 Spring Boot 配置体系理解成:

text 复制代码
Spring Boot 提供默认配置
             ↓
开发者按需进行覆盖
             ↓
最终形成应用实际配置

学习 Spring Boot 配置时,不只是记住某个属性叫什么,还要理解:

  1. 这个属性控制哪个功能;
  2. 不配置时默认行为是什么;
  3. 配置后改变了什么;
  4. 是否能通过代码方式自定义;
  5. 不同配置来源的优先级如何。

十一、Spring Boot 的主要组成部分

从学习角度,可以把 Spring Boot 分成以下部分:

text 复制代码
Spring Boot
├── 启动机制
├── 自动配置
├── Starter
├── 配置文件
├── 配置绑定
├── 环境与 Profile
├── 日志系统
├── Web 应用支持
├── 测试支持
├── 开发工具
├── Actuator
├── 应用打包
└── 应用运行与生产配置

其中最核心的主线是:

text 复制代码
启动
  ↓
加载配置
  ↓
创建容器
  ↓
扫描组件
  ↓
执行自动配置
  ↓
创建 Bean
  ↓
启动应用

十二、Spring Boot、Starter、自动配置之间的关系

这是入门阶段最容易混淆的地方。

Starter: 负责准备某个开发场景需要的组件

自动配置: 负责根据当前条件配置这些组件

配置文件: 负责允许开发者修改默认行为

SpringApplication: 负责启动并组织整个应用

它们组合起来:

text 复制代码
Starter 准备组件
        ↓
SpringApplication 启动应用
        ↓
自动配置判断条件
        ↓
创建并配置相关对象
        ↓
配置文件覆盖默认值
        ↓
应用运行
相关推荐
前端工作日常2 小时前
我学习到的Java程序的生命周期
java·后端
SunnyDays10112 小时前
Java 实现 PDF 页面删除、排序、旋转和裁剪
java·pdf
人工干智能2 小时前
科普:Pandas 索引器 loc 与 iloc 的编程思维
java·人工智能·pandas
持力行3 小时前
C++与Java变量声明、定义及内存分配的核心区别
java·开发语言·c++
AskHarries3 小时前
GitHub 登录配置流程
后端
接着奏乐接着舞5 小时前
大模型开发 agent 知识库
java
xuhaoyu_cpp_java5 小时前
SpringBoot学习(四)
java·经验分享·spring boot·笔记·学习
Sinclair5 小时前
安企CMS的安装-常见安装问题排查
运维·后端
Sinclair6 小时前
安企CMS的安装-安装引导与初始化
运维·后端