# SpringBoot 前置知识

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)


在深入 SpringBoot 特性及源码分析之前,先弄清楚两个问题:

  • 为什么在已有 Spring 框架的前提下,还需要 SpringBoot?
  • SpringBoot 的核心特性是什么?

# 为什么要SpringBoot ?

在传统 Spring 框架中,开发一个 Web 项目繁琐且模板化。例如:

  1. 创建 Maven/Gradle 项目结构
  2. 引入 Spring、Spring MVC、Servlet API 等依赖
  3. 配置 web.xml,注册 DispatcherServlet
  4. 配置 DispatcherServlet.xml 或 Spring MVC 配置类
  5. 编写 Controller 处理请求
  6. 部署到 JSP/Servlet 容器

实际开发中,只有第 5 步是研发人员真正需要关注的业务逻辑,其他步骤都是重复工作。 SpringBoot 诞生的目标,就是约定优于配置,帮助开发者快速创建 Spring 项目,减少配置和模板化步骤。

# 什么是SpringBoot?

SpringBoot 是基于 Spring 的一套快速开发脚手架,核心思想是约定优于配置

特点:

  1. 内置容器 引入 spring-boot-starter-web 后自动内置 Tomcat,无需手动配置外部容器。
  2. 固定的项目结构 提供默认的目录结构规范,降低项目搭建门槛。
  3. 简化的配置文件 提供 application.propertiesapplication.yml,结合 @Value@ConfigurationProperties 读取配置。
  4. Starter 启动依赖 引入 starter 即可自动拉取一系列相关依赖,避免手动管理版本和配置冲突。

# SpringBoot 核心特性

特性 说明
EnableAutoConfiguration 自动装配 自动加载符合条件的 Bean,减少手动配置。
Starter 启动依赖 模块化依赖管理,直接引入 Starter,即可快速集成功能模块。
Actuator 监控 提供丰富的健康检查和指标监控端点,支持 HTTP/JMX 访问。
Spring Boot CLI 命令行工具,支持 Groovy 脚本快速开发。

上文中的特性都依赖于注解驱动的发展, 如果没有注解仍然以配置文件的形式, SpringBoot项目依旧会相当繁琐。

# Spring 注解驱动的发展历程

# Spring1.x

  • Java 5 刚发布,Spring 引入注解如 @Transactional,但配置方式仍然依赖 XML。

    <bean id="userService" class="com.example.UserService"/>
    
    1

# Spring 2.x 时代

  • 核心注解: @Autowired@Qualifier@Component@Service@Repository@Controller
  • 依旧需要 context:annotation-configcontext:component-scan 配置。
<context:annotation-config/>
<context:component-scan base-package="com.example"/>
1
2

# Spring 3.x 时代

  • 推出 @Configuration 注解,支持 Java 配置类,逐渐取代 XML。
  • @Import: 将传入的类注册到 Spring IOC 容器中,可以是普通类、配置类、实现特定接口的类,最终都会被解析为 BeanDefinition 并注册。
  • 开始全面拥抱 JavaConfig。
@Configuration
@ComponentScan("com.example")
public class AppConfig {

    @Bean
    public UserService userService() {
        return new UserService();
    }
}
1
2
3
4
5
6
7
8
9

Enable 模块驱动

  • 通过 @EnableXXX 注解实现模块化启用

image-20250902100652151

# Spring 4.x 时代

  • 引入 条件化配置 和更强的 JavaConfig 支持。
  • 核心注解:
    • @Conditional:基于条件动态注册 Bean。
    • @RestController:组合注解,简化 REST 风格接口开发。
    • @EventListener:简化事件监听。
    • 全面支持 Java 8 特性(Lambda、Streams 等)。
@Configuration
public class ConditionalConfig {

    @Bean
    @Conditional(OnWindowsCondition.class)  // 仅在 Windows 系统下注册
    public WindowsService windowsService() {
        return new WindowsService();
    }
}
1
2
3
4
5
6
7
8
9

# Spring 5.x 时代

  • 强调 函数式编程响应式编程性能优化
  • 核心注解:
    • @Indexed:加快组件扫描速度,提升启动性能。
    • @Nullable:增强空值检查提示。
    • 全面支持 JDK 9 模块系统和 Java 11。
    • 引入 Spring WebFlux:基于 Reactor 的响应式编程模型。
@Indexed
@Component
public class FastScannedComponent {
    // 使用 @Indexed 后可通过提前生成的 META-INF 文件加快类扫描
}
1
2
3
4
5

响应式 Controller 示例:

@RestController
@RequestMapping("/api")
public class ReactiveController {

    @GetMapping("/hello")
    public Mono<String> hello() {
        return Mono.just("Hello, Spring 5 WebFlux!");
    }
}
1
2
3
4
5
6
7
8
9

# Spring Bean的装载方式

Spring 的核心思想是 IOC(控制反转),Bean 的创建和生命周期管理都交给容器完成。 在实际开发中,Bean 的装载方式可以分为 IOC 静态装载动态 Bean 装载 两种:

  • 静态装载:Bean 定义在项目启动时就确定并加载,适用于绝大多数业务逻辑。
  • 动态装载:Bean 可以在运行时按需加载、注册或替换,适用于复杂或可扩展的场景。

# IOC 装载

IOC 静态装载是 Spring 的默认加载方式,容器启动时就会扫描、解析和创建 BeanDefinition,并实例化 Bean。

常见实现方式:

  1. XML 配置方式(早期主流)

    <beans xmlns="http://www.springframework.org/schema/beans">
        <bean id="userService" class="com.example.UserService"/>
    </beans>
    
    1
    2
    3
  2. 注解扫描方式

    @Configuration
    @ComponentScan("com.example")
    public class AppConfig {
    }
    @Service
    public class UserService {
        public void hello() {
            System.out.println("Hello Spring!");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  3. JavaConfig + @Bean 方式

    @Configuration
    public class BeanConfig {
    
        @Bean
        public UserService userService() {
            return new UserService();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

场景:适用于项目核心业务 Bean 的加载,大部分开发只需使用这种方式。

# 动态 Bean 装载

为什么需要动态装载?

  • 插件化开发:无需重启服务即可加载新功能模块。
  • 多租户/多环境:不同租户或环境需要不同 Bean。
  • Starter 自动装配:Spring Boot Starter 底层依赖动态注册机制。
  • 灰度发布/热更新:可在线动态替换 Bean,提升系统灵活性。
  • 按需加载:延迟加载复杂 Bean,提高启动性能。

实现方式:

  1. BeanDefinitionRegistry 动态注册

    在Spring章节 @EnableAspectJAutoProxy中使用的就是BeanDefinitionRegistry 进行动态注册。

    简单示例

    @Component
    public class DynamicBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(UserService.class);
            registry.registerBeanDefinition("userService", beanDefinition);
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {}
    }
    
    @Configuration
    @Import(DynamicBeanRegistrar.class)
    public class AppConfig {
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    在容器初始化 BeanDefinition 阶段,手动注册新的 Bean 定义。

  2. ImportSelector 动态导入

    public class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata metadata) {
            return new String[]{"com.example.UserService"};
        }
    }
    
    @Configuration
    @Import(MyImportSelector.class)
    public class AppConfig {
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    Spring Boot Starter 底层大量使用此机制实现按需加载。可以批量导入

# 常见组合

在 Spring 和 Spring Boot 源码中,@Import@Conditional 常常与 ImportSelectorImportBeanDefinitionRegistrar 搭配使用,形成一套“组合拳”来完成 Bean 的动态装载。这套机制是 Spring Boot 自动配置、Starter 组件、模块化开发的底层核心。

组合方式 说明 典型场景
@Import + ImportSelector 通过 ImportSelector 返回一批要注册的类名,容器自动将这些类转换成 BeanDefinition 注入容器中。 Spring Boot 自动装配,批量按需注册配置类
@Import + @Conditional + ImportSelector 结合条件注解,在满足特定条件(如类路径存在、配置文件开关、环境变量等)时选择性注册 Bean,实现真正的“按需加载”。 Spring Boot Starter 自动装配核心
@Import + ImportBeanDefinitionRegistrar 通过 ImportBeanDefinitionRegistrar 直接操作 BeanDefinitionRegistry,实现更灵活、更复杂的 Bean 注册逻辑。 MyBatis Mapper 扫描、Spring Security 等框架底层
@EnableXXX + @Import 对外封装为一个简洁的开关注解,让用户一键启用模块,而模块内部通过 @Import 完成复杂 Bean 装载。 @EnableScheduling、@EnableAsync 等
@Import + BeanDefinitionRegistryPostProcessor 在容器刷新前修改 Bean 注册表,是更底层的扩展点,适合框架作者使用。 Dubbo、Spring Cloud 等中间件框架

# SPI 机制

在此处再次强调SPI机制, SpringBoot的starter加载也就是自动装配原理实际上是SPI机制的变形, 本篇文章先介绍SPI的原理

# SPI 是什么

SPI(Service Provider Interface)是一种 服务发现机制,允许框架或平台在运行时动态加载第三方实现。 它通过在类路径下约定文件目录和内容,实现“接口与实现解耦”,使得框架能够在不修改代码的前提下,自动发现和加载实现类。

通俗理解:接口由框架定义,实现由第三方开发,JDK/Spring 通过 SPI 在运行时帮你找实现并加载。

# JDK 原生 SPI 原理

JDK 从 java.util.ServiceLoader 开始提供 SPI 支持,核心机制是 类路径扫描 + 反射实例化

  • 约定路径META-INF/services/
  • 文件命名:文件名是接口的全类名
  • 文件内容:实现类的全类名
  • 加载逻辑ServiceLoader.load(接口.class) 会扫描文件并加载实现类实例

示例:

// 接口
public interface Logger {
    void log(String msg);
}

// 实现类
public class ConsoleLogger implements Logger {
    @Override
    public void log(String msg) {
        System.out.println("Console: " + msg);
    }
}

// META-INF/services/com.example.Logger 文件内容:
com.example.ConsoleLogger

// 使用
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
for (Logger logger : loader) {
    logger.log("Hello SPI");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Spring Boot Starter 与 SPI

Spring Boot 的自动装配原理是 SPI 的升级版,区别在于它做了更多优化:

对比项 JDK SPI Spring Boot SPI(变形)
配置文件路径 META-INF/services/接口全类名 META-INF/spring.factories(Spring Boot 2.x 前) META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)
加载方式 ServiceLoader SpringFactoriesLoader
加载内容 实现类全类名 自动配置类全类名
加载时机 手动调用加载 启动时自动加载
额外功能 条件化加载(@Conditional)、按需注入等