【2】Spring Boot自动装配

Spring Boot 自动装配的核心是 "约定大于配置":通过@EnableAutoConfiguration触发,加载spring.factories中的自动配置类,结合条件注解动态注入 Bean;

自定义 Starter 则是遵循该机制,封装核心功能 + 配置属性 + 自动配置类,实现 "引入即生效" 的开箱即用能力。

一、Spring Boot 自动装配梳理

复制代码
Spring Boot 自动装配
├─ 1. 注解驱动入口
│  ├─ @SpringBootApplication(复合注解)
│  │  ├─ @SpringBootConfiguration(本质@Configuration)
│  │  ├─ @EnableAutoConfiguration(自动装配核心)
│  │  └─ @ComponentScan(组件扫描范围)
│  └─ 启动类入口
│     └─ SpringApplication.run(启动类.class, args)(触发装配)
├─ 2. 自动装配核心机制
│  ├─ @EnableAutoConfiguration 作用
│  │  ├─ 导入 AutoConfigurationImportSelector 类
│  │  └─ 触发Spring扫描自动配置类
│  └─ AutoConfigurationImportSelector 核心特性
│     ├─ 实现 DeferredImportSelector 接口(延迟导入)
│     └─ 重写 selectImports() 方法(核心入口)
├─ 3. 配置类选择过程
│  ├─ selectImports() 方法逻辑
│  │  └─ 调用 getAutoConfigurationEntry() 获取配置项
│  └─ getAutoConfigurationEntry() 核心步骤
│     ├─ 加载候选配置类(从spring.factories读取)
│     ├─ 去重处理(避免重复配置)
│     ├─ 条件过滤(基于@Conditional家族注解)
│     └─ 排除指定配置(spring.autoconfigure.exclude)
├─ 4. spring.factories 加载机制
│  ├─ SpringFactoriesLoader 工具类
│  │  ├─ 扫描所有Jar包的 META-INF/spring.factories
│  │  ├─ 读取 EnableAutoConfiguration 对应的配置类列表
│  │  └─ 合并多个Jar包中的配置(去重)
│  └─ spring.factories 配置格式
│     └─ 键值对:org.springframework.boot.autoconfigure.EnableAutoConfiguration=配置类全限定名(多个用逗号分隔)
├─ 5. 条件过滤机制(配置类生效规则)
│  ├─ @Conditional 注解家族(核心过滤逻辑)
│  │  ├─ @ConditionalOnClass:类路径存在指定类时生效
│  │  ├─ @ConditionalOnMissingClass:类路径不存在指定类时生效
│  │  ├─ @ConditionalOnBean:容器中存在指定Bean时生效
│  │  ├─ @ConditionalOnMissingBean:容器中不存在指定Bean时生效
│  │  ├─ @ConditionalOnProperty:配置文件存在指定属性时生效
│  │  └─ @ConditionalOnWebApplication:Web环境下生效
│  └─ 条件评估器
│     └─ ConditionEvaluator(Spring内部评估条件是否满足)
└─ 6. Bean注册与应用启动
   ├─ 配置类解析
   │  └─ ConfigurationClassParser(解析@Configuration类)
   ├─ Bean定义注册
   │  └─ BeanDefinitionRegistry(将配置类中的Bean注册到容器)
   └─ 应用启动完成
      └─ ApplicationContext 初始化完成(Bean实例化、依赖注入完成)

二、Spring Boot 自动装配加载过程分析

1. 自动装配入口:@SpringBootApplication

@SpringBootApplication 是 Spring Boot 应用的核心注解,它是一个组合注解,包含了三个关键注解:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
    // ...
}

其中,@EnableAutoConfiguration 是自动装配的核心入口点。

2. 自动装配核心:@EnableAutoConfiguration

@EnableAutoConfiguration 注解通过 @Import 引入了 AutoConfigurationImportSelector

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // ...
}

@Import(AutoConfigurationImportSelector.class) 是自动装配的关键,它会导入所有符合条件的自动配置类。

3. 配置类选择器:AutoConfigurationImportSelector

AutoConfigurationImportSelector 实现了 ImportSelector 接口,其核心方法是 selectImports()

java 复制代码
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

该方法会调用 getAutoConfigurationEntry() 来获取需要导入的自动配置类。

4. 获取自动配置条目:getAutoConfigurationEntry

getAutoConfigurationEntry() 方法是获取自动配置类的核心:

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 1. 加载所有候选配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 2. 去重
    configurations = removeDuplicates(configurations);
    // 3. 应用排除规则
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 4. 应用过滤规则
    configurations = getConfigurationClassFilter().filter(configurations);
    // 5. 触发自动配置导入事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

5. 加载候选配置类:getCandidateConfigurations

getCandidateConfigurations() 方法通过 SpringFactoriesLoader 加载 spring.factories 文件中的配置类:

java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    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;
}

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

6. SpringFactoriesLoader 加载机制

SpringFactoriesLoader 是 Spring 框架用于加载工厂类的工具,它会从所有 JAR 包的 META-INF/spring.factories 文件中读取配置:

java 复制代码
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 缓存机制,避免重复加载
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // 加载所有 META-INF/spring.factories 文件
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    } catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

其中,FACTORIES_RESOURCE_LOCATION 常量定义为 META-INF/spring.factories

7. 条件过滤机制

加载到候选配置类后,Spring Boot 会应用条件注解进行过滤,常用的条件注解包括:

  • @ConditionalOnClass:当类路径存在指定类时生效
  • @ConditionalOnMissingClass:当类路径不存在指定类时生效
  • @ConditionalOnBean:当容器中存在指定 Bean 时生效
  • @ConditionalOnMissingBean:当容器中不存在指定 Bean 时生效
  • @ConditionalOnProperty:当配置属性满足指定条件时生效
  • @ConditionalOnWebApplication:当应用是 Web 应用时生效
  • @ConditionalOnNotWebApplication:当应用不是 Web 应用时生效

8. Bean 注册过程

经过条件过滤后,符合条件的配置类会被注册到 Spring 容器中,这些配置类通常会使用 @Bean 注解定义各种 Bean。

9. 应用启动完成

当所有自动配置类都被处理完成后,Spring Boot 应用就启动完成了,所有配置的 Bean 都已经注册到容器中。

总结

Spring Boot 自动装配的完整过程可以总结为:

  1. @SpringBootApplication 组合注解启动应用
  2. @EnableAutoConfiguration 启用自动装配
  3. @Import(AutoConfigurationImportSelector) 导入配置选择器
  4. AutoConfigurationImportSelector.selectImports() 选择需要导入的配置类
  5. SpringFactoriesLoader.loadFactoryNames() 加载 spring.factories 中的配置类
  6. 应用去重、排除和条件过滤
  7. 注册符合条件的配置类和 Bean
  8. 应用启动完成

三、配置校验 Starter 使用示例

1. 示例项目说明

本示例展示如何在 Spring Boot 项目中使用配置校验 Starter。

2. 项目结构

复制代码
config-validator-demo/
├── src/main/java/com/example/demo/
│   └── DemoApplication.java        # 主应用类
├── src/main/resources/
│   └── application.properties      # 配置文件
└── pom.xml                         # 项目依赖配置

3. 添加依赖

pom.xml 文件中添加配置校验 Starter 依赖:

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 配置校验 Starter -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>config-validator-spring-boot-starter</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

4. 主应用类

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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

5. 配置文件示例

5.1 正确的配置 (application.properties)

properties 复制代码
# 服务器配置
server.port=8080

# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=root
spring.datasource.password=password

# 配置校验配置
config.validator.enabled=true
config.validator.fail-fast=true
config.validator.required-properties=spring.datasource.url,spring.datasource.username,spring.datasource.password
config.validator.ignored-properties=spring.datasource.driver-class-name

5.2 错误的配置 (application.properties)

properties 复制代码
# 服务器配置
server.port=99999    # 无效的端口号

# 数据库配置
spring.datasource.url=invalid-url  # 无效的数据库URL
# spring.datasource.username  # 缺少必需的配置
spring.datasource.password=password

# 配置校验配置
config.validator.enabled=true
config.validator.fail-fast=true
config.validator.required-properties=spring.datasource.url,spring.datasource.username,spring.datasource.password

6. 运行效果

6.1 配置正确时的输出

复制代码
=================== 配置校验成功 ===================
所有必需的配置属性都已正确配置!
================================================

6.2 配置错误时的输出

复制代码
=================== 配置校验失败 ===================
ERROR: server.port - 端口号必须是0-65535之间的整数
ERROR: spring.datasource.url - 数据库URL格式不正确
ERROR: spring.datasource.username - 配置属性必须存在且不为空
================================================

7. 测试端点

可以添加一个测试端点来验证配置是否生效:

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigController {
    
    @Autowired
    private Environment environment;
    
    @GetMapping("/config")
    public String getConfig() {
        String port = environment.getProperty("server.port");
        String dbUrl = environment.getProperty("spring.datasource.url");
        return String.format("Server Port: %s, Database URL: %s", port, dbUrl);
    }
}

8. 快速失败模式测试

  1. 在配置文件中设置 config.validator.fail-fast=true
  2. 故意遗漏一个必需的配置属性
  3. 启动应用,应用应该会在启动时抛出异常并终止

9. 忽略属性测试

  1. 在配置文件中设置 config.validator.ignored-properties=spring.datasource.username
  2. 故意遗漏 spring.datasource.username 配置
  3. 启动应用,应用应该会正常启动,不会因为缺少该配置而失败

10. 禁用配置校验

在配置文件中设置 config.validator.enabled=false 可以禁用配置校验功能。

四、相关资源

相关推荐
jimy12 小时前
ps aux|grep pid 和 ps -p pid 的区别
java·linux·开发语言
weixin_437546332 小时前
注释文件夹下脚本的Debug
java·linux·算法
Python极客之家2 小时前
基于Django的高校二手市场与社交系统
后端·python·数据挖掘·django·毕业设计
白露与泡影3 小时前
Java关键字解析之final:不可变的本质、设计哲学与并发安全
java·开发语言·安全
Li_7695323 小时前
IDEA 中 maven 图标失踪解决措施
java·maven·intellij-idea
月明长歌3 小时前
【码道初阶】【LeetCode 150】逆波兰表达式求值:为什么栈是它的最佳拍档?
java·数据结构·算法·leetcode·后缀表达式
想用offer打牌3 小时前
一站式了解长轮询,SSE和WebSocket
java·网络·后端·websocket·网络协议·系统架构
Vespeng3 小时前
利用周末写一个小工具:多设备预览图生成
后端·开源·go
C雨后彩虹3 小时前
最大数字问题
java·数据结构·算法·华为·面试