SpringBoot自动配置原理深度解析

💥 开场:一次"见鬼"的经历

时间: 周一早上
地点: 办公室
事件: 新项目启动

我: "今天要集成Redis,准备写配置类..."(打开IDE)

哈吉米路过: "集成Redis?直接用就行了啊。"

我: "不配置?不可能吧?" 🤔

哈吉米: "你试试。"


我半信半疑地写了代码:

// 1. pom.xml只加了依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

// 2. application.yml只写了连接信息
spring:
  redis:
    host: localhost
    port: 6379

// 3. Service直接注入使用
@Service
public class UserService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;  // 直接注入
    
    public void saveUser(String key, User user) {
        redisTemplate.opsForValue().set(key, user);  // 直接用
    }
}

启动项目,测试...

卧槽!真的可以! 😱


我: "这是什么妖术?RedisTemplate是谁创建的?我明明没写配置类啊!"

哈吉米: "这就是SpringBoot的自动配置,它会自动帮你配置好一切。"

我: "怎么做到的?" 🤔

哈吉米: "核心就两个东西:@EnableAutoConfiguration注解 和 spring.factories文件。"

南北绿豆走过来: "我给你画个图,你就明白了..."

阿西噶阿西也凑过来: "对对对,我之前也研究过这个,贼牛逼!"


🎯 第一问:什么是自动配置?

传统Spring vs SpringBoot

传统Spring配置Redis:

// 1. 引入依赖(多个)
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
</dependency>

// 2. 写配置类(50+行代码)
@Configuration
public class RedisConfig {
    
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName("localhost");
        factory.setPort(6379);
        factory.setDatabase(0);
        factory.setTimeout(2000);
        factory.setUsePool(true);
        
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(100);
        poolConfig.setMaxIdle(20);
        poolConfig.setMinIdle(5);
        factory.setPoolConfig(poolConfig);
        
        return factory;
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        // 配置序列化器
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

累不累? 累死了!😫


SpringBoot自动配置:

// 1. 引入一个依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

// 2. 写几行配置
spring:
  redis:
    host: localhost
    port: 6379

// 3. 直接用
@Autowired
private RedisTemplate<String, Object> redisTemplate;

爽不爽? 爽!😎


什么是自动配置?

南北绿豆: "自动配置就是SpringBoot根据你引入的依赖,自动帮你配置好相应的Bean。"

官方定义:

Auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added.

人话版本:

你引入了什么依赖
    ↓
SpringBoot检测到了
    ↓
自动创建相应的Bean
    ↓
你直接用就行

类比:

  • 传统Spring = 自己做饭(买菜、洗菜、炒菜)
  • SpringBoot自动配置 = 点外卖(厨师做好送到家)

自动配置的价值

阿西噶阿西: "自动配置带来三大好处!"

1. 零配置启动

// 这就是一个完整的SpringBoot应用
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

没有任何XML配置!没有任何Java配置类!


2. 约定大于配置

// SpringBoot有默认配置
spring:
  redis:
    host: localhost     # 默认
    port: 6379          # 默认
    database: 0         # 默认
    timeout: 2000       # 默认

// 你只需要改不同的
spring:
  redis:
    host: my-redis.com  # 只改这个

3. 快速集成第三方框架

// 集成MyBatis
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

// 集成RabbitMQ
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

// 集成Elasticsearch
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

// 全部自动配置,开箱即用!

🔍 第二问:@SpringBootApplication三合一注解

注解源码

哈吉米: "我们先从启动类的注解开始看。"

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

点进去看看:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration        // ← 1. 配置类
@EnableAutoConfiguration        // ← 2. 启用自动配置(核心!)
@ComponentScan(excludeFilters = {  // ← 3. 组件扫描
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
    // ...
}

发现: @SpringBootApplication是三个注解的组合!


三合一解析

注解1:@SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration  // ← 本质就是@Configuration
public @interface SpringBootConfiguration {
}

作用: 标记这是一个配置类,等同于@Configuration


注解2:@ComponentScan

@ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})

作用: 扫描当前包及子包下的@Component@Service@Controller

默认扫描规则:

启动类在:com.example.app
    ↓
扫描范围:com.example.app 及其子包
    ├─ com.example.app.controller  ✅
    ├─ com.example.app.service     ✅
    └─ com.example.app.mapper      ✅

不在范围内:
    └─ com.example.other           ❌(不会扫描)

这就是开篇404的原因! 💡


注解3:@EnableAutoConfiguration(核心!)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)  // ← 关键!
public @interface EnableAutoConfiguration {
    
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
    /**
     * 排除指定的自动配置类
     */
    Class<?>[] exclude() default {};
    
    /**
     * 排除指定名称的自动配置类
     */
    String[] excludeName() default {};
}

南北绿豆: "看到了吗?@Import(AutoConfigurationImportSelector.class),这就是自动配置的入口!"


🪄 第三问:@EnableAutoConfiguration的魔法

@Import机制回顾

阿西噶阿西: "先复习一下@Import的作用。"

// @Import可以导入三种东西

// 1. 导入普通类
@Import(MyConfig.class)
public class AppConfig {
}

// 2. 导入ImportSelector(返回要导入的类名)
@Import(MyImportSelector.class)
public class AppConfig {
}

// 3. 导入ImportBeanDefinitionRegistrar(手动注册Bean)
@Import(MyImportBeanDefinitionRegistrar.class)
public class AppConfig {
}

SpringBoot用的是第2种:ImportSelector!


AutoConfigurationImportSelector源码分析

位置: org.springframework.boot.autoconfigure.AutoConfigurationImportSelector

核心方法:selectImports()

public class AutoConfigurationImportSelector implements DeferredImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        
        // 1. 检查自动配置是否启用
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        
        // 2. 获取自动配置的元数据
        AutoConfigurationMetadata autoConfigurationMetadata = 
            AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        
        // 3. 获取自动配置的Entry(核心!)
        AutoConfigurationEntry autoConfigurationEntry = 
            getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        
        // 4. 返回要导入的配置类名称数组
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

哈吉米: "重点看getAutoConfigurationEntry()方法!"


核心方法:getAutoConfigurationEntry()

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    
    // 1. 获取注解属性(exclude、excludeName)
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    
    // 2. 获取候选的自动配置类(核心!)
    List<String> configurations = getCandidateConfigurations(
        annotationMetadata, attributes);
    
    // 3. 去重
    configurations = removeDuplicates(configurations);
    
    // 4. 获取需要排除的配置类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    
    // 5. 检查排除类是否合法
    checkExcludedClasses(configurations, exclusions);
    
    // 6. 移除排除的配置类
    configurations.removeAll(exclusions);
    
    // 7. 过滤(根据条件注解)
    configurations = filter(configurations, autoConfigurationMetadata);
    
    // 8. 触发自动配置导入事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    
    // 9. 返回结果
    return new AutoConfigurationEntry(configurations, exclusions);
}

南北绿豆: "看到了吗?第2步getCandidateConfigurations()就是读取spring.factories文件!"


关键方法:getCandidateConfigurations()

protected List<String> getCandidateConfigurations(
        AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    // 使用SpringFactoriesLoader加载配置类
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(),  // EnableAutoConfiguration.class
        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;
}

阿西噶阿西: "重点来了!SpringFactoriesLoader.loadFactoryNames()!"


📄 第四问:spring.factories文件的魔法

SpringFactoriesLoader源码

位置: org.springframework.core.io.support.SpringFactoriesLoader

public final class SpringFactoriesLoader {
    
    /**
     * spring.factories文件的位置(固定)
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    /**
     * 加载指定类型的工厂名称
     */
    public static List<String> loadFactoryNames(Class<?> factoryType, 
            @Nullable ClassLoader classLoader) {
        
        String factoryTypeName = factoryType.getName();
        
        // 1. 加载所有spring.factories文件的内容
        return loadSpringFactories(classLoader)
            // 2. 获取指定key的值
            .getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
    /**
     * 加载所有spring.factories文件
     */
    private static Map<String, List<String>> loadSpringFactories(
            @Nullable ClassLoader classLoader) {
        
        // 1. 先从缓存获取
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
        
        try {
            // 2. 扫描所有jar包的META-INF/spring.factories文件
            Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            
            result = new LinkedMultiValueMap<>();
            
            // 3. 遍历所有找到的文件
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                
                // 4. 读取properties文件
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                
                // 5. 解析每一行
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    
                    // 6. 分割多个值(逗号分隔)
                    for (String factoryImplementationName : 
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            
            // 7. 放入缓存
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException(
                "Unable to load factories from location [" + 
                FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
}

哈吉米: "看明白了吗?SpringBoot会扫描所有jar包的META-INF/spring.factories文件!"


spring.factories文件格式

位置: spring-boot-autoconfigure-xxx.jar!/META-INF/spring.factories

内容示例:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
...

南北绿豆: "SpringBoot 2.7.x有133个自动配置类!"


自动配置类加载流程图

@SpringBootApplication@EnableAutoConfiguration@Import(AutoConfigurationImportSelector.class)
    ↓
selectImports()方法
    ↓
getAutoConfigurationEntry()
    ↓
getCandidateConfigurations()
    ↓
SpringFactoriesLoader.loadFactoryNames()
    ↓
扫描所有jar包的META-INF/spring.factories
    ↓
读取EnableAutoConfiguration对应的配置类
    ↓
返回133个自动配置类的全限定名
    ↓
经过条件注解过滤
    ↓
注册符合条件的Bean

🎛️ 第五问:条件注解过滤机制

为什么需要条件注解?

阿西噶阿西: "Spring

Boot加载了133个自动配置类,但不是所有都会生效!"

场景:

// RedisAutoConfiguration
@Configuration
@ConditionalOnClass(RedisOperations.class)  // ← 条件:Redis类存在
public class RedisAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean  // ← 条件:没有RedisTemplate Bean
    public RedisTemplate<Object, Object> redisTemplate(...) {
        // ...
    }
}

如果你没有引入Redis依赖:

检查条件:RedisOperations.class存在吗?
    ↓
不存在!
    ↓
不满足条件,不加载这个配置类 ✅

如果你引入了Redis依赖:

检查条件:RedisOperations.class存在吗?
    ↓
存在!
    ↓
满足条件,加载这个配置类 ✅
    ↓
创建RedisTemplate Bean

常用条件注解

@ConditionalOnClass

/**
 * 当指定的类在classpath中存在时,配置才生效
 */
@ConditionalOnClass(RedisOperations.class)
public class RedisAutoConfiguration {
}

使用场景: 判断是否引入了某个依赖


@ConditionalOnMissingClass

/**
 * 当指定的类在classpath中不存在时,配置才生效
 */
@ConditionalOnMissingClass("com.example.SomeClass")
public class MyAutoConfiguration {
}

@ConditionalOnBean

/**
 * 当容器中存在指定Bean时,配置才生效
 */
@ConditionalOnBean(DataSource.class)
public class JpaAutoConfiguration {
}

使用场景: 某个配置依赖另一个Bean


@ConditionalOnMissingBean

/**
 * 当容器中不存在指定Bean时,配置才生效
 */
@Bean
@ConditionalOnMissingBean
public RedisTemplate<Object, Object> redisTemplate(...) {
    // 用户没有自定义RedisTemplate,才创建默认的
}

使用场景: 提供默认实现,但允许用户覆盖


@ConditionalOnProperty

/**
 * 当指定的配置属性满足条件时,配置才生效
 */
@ConditionalOnProperty(
    prefix = "spring.redis",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true  // 配置缺失时默认为true
)
public class RedisAutoConfiguration {
}

配置文件:

# 启用Redis自动配置
spring:
  redis:
    enabled: true

# 禁用Redis自动配置
spring:
  redis:
    enabled: false

@ConditionalOnWebApplication

/**
 * 当应用是Web应用时,配置才生效
 */
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class WebMvcAutoConfiguration {
}

@ConditionalOnExpression

/**
 * 当SpEL表达式为true时,配置才生效
 */
@ConditionalOnExpression("${my.enabled:true} and ${my.mode} == 'fast'")
public class MyAutoConfiguration {
}

条件注解判断流程

哈吉米: "条件注解是怎么判断的?"

源码位置: org.springframework.boot.autoconfigure.condition.OnClassCondition

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, 
        AnnotatedTypeMetadata metadata) {
    
    ClassLoader classLoader = context.getClassLoader();
    ConditionMessage matchMessage = ConditionMessage.empty();
    
    // 获取@ConditionalOnClass注解的value
    List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
    
    if (onClasses != null) {
        // 检查哪些类不存在
        List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
        
        if (!missing.isEmpty()) {
            // 有类不存在,条件不满足
            return ConditionOutcome.noMatch(
                ConditionMessage.forCondition(ConditionalOnClass.class)
                    .didNotFind("required class", "required classes")
                    .items(Style.QUOTE, missing));
        }
        
        // 所有类都存在,条件满足
        matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
            .found("required class", "required classes")
            .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
    }
    
    return ConditionOutcome.match(matchMessage);
}

过滤流程:

133个自动配置类
    ↓
@ConditionalOnClass过滤 → 剩余50个
    ↓
@ConditionalOnBean过滤 → 剩余30个
    ↓
@ConditionalOnProperty过滤 → 剩余20个
    ↓
最终生效的配置类

💻 第六问:实战案例 - Redis自动配置深度剖析

RedisAutoConfiguration源码

位置: org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)  // ← 条件1:Redis类存在
@EnableConfigurationProperties(RedisProperties.class)  // ← 启用配置属性
@Import({  // ← 导入其他配置类
    LettuceConnectionConfiguration.class,
    JedisConnectionConfiguration.class
})
public class RedisAutoConfiguration {
    
    /**
     * 创建RedisTemplate
     */
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")  // ← 条件2:用户没自定义
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)  // ← 条件3:有连接工厂
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    
    /**
     * 创建StringRedisTemplate
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

RedisProperties配置属性类

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    
    /**
     * Redis服务器地址
     */
    private String host = "localhost";
    
    /**
     * Redis服务器端口
     */
    private int port = 6379;
    
    /**
     * 数据库索引
     */
    private int database = 0;
    
    /**
     * 密码
     */
    private String password;
    
    /**
     * 连接超时时间
     */
    private Duration timeout;
    
    /**
     * 连接池配置
     */
    private Pool pool;
    
    // Getter和Setter...
}

LettuceConnectionConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)  // ← Lettuce客户端存在
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", 
    matchIfMissing = true)  // ← 默认使用Lettuce
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    LettuceConnectionFactory redisConnectionFactory(
            ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
            ClientResources clientResources) {
        
        LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(
            builderCustomizers, clientResources, getProperties().getLettuce().getPool());
        
        return createLettuceConnectionFactory(clientConfig);
    }
    
    private LettuceConnectionFactory createLettuceConnectionFactory(
            LettuceClientConfiguration clientConfiguration) {
        
        if (getSentinelConfig() != null) {
            return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
        }
        if (getClusterConfiguration() != null) {
            return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
        }
        return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
    }
}

自动配置生效流程

南北绿豆: "我们来梳理一下Redis自动配置的完整流程:"

1. 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
    ↓
2. SpringBoot启动,扫描spring.factories
    ↓
3. 发现RedisAutoConfiguration
    ↓
4. 检查条件注解
    ├─ @ConditionalOnClass(RedisOperations.class) → ✅ 存在
    ├─ @ConditionalOnMissingBean(name = "redisTemplate") → ✅ 用户没自定义
    └─ @ConditionalOnSingleCandidate(RedisConnectionFactory.class) → ✅ 有连接工厂
    ↓
5. 条件全部满足,创建Bean
    ├─ LettuceConnectionFactory(连接工厂)
    ├─ RedisTemplate(操作模板)
    └─ StringRedisTemplate(字符串模板)
    ↓
6. 读取配置文件
spring:
  redis:
    host: localhost
    port: 63797. 绑定到RedisProperties
    ↓
8. 注入到ConnectionFactory
    ↓
9. 自动配置完成!

🔧 第七问:手写简易版自动配置

目标

实现一个最简单的自动配置机制,支持:

  1. ✅ 读取spring.factories文件
  2. ✅ 加载自动配置类
  3. ✅ 简单的条件注解判断
  4. ✅ 注册Bean到容器

实现代码

1. 自定义注解

package com.example.autoconfig;

import java.lang.annotation.*;

/**
 * 启用自动配置
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SimpleAutoConfigurationImportSelector.class)
public @interface EnableSimpleAutoConfiguration {
}

/**
 * 条件注解:类存在
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionalOnClass {
    Class<?>[] value();
}

2. 简易版ImportSelector

package com.example.autoconfig;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;

import java.util.List;

/**
 * 简易版自动配置导入选择器
 */
public class SimpleAutoConfigurationImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        
        System.out.println("===== 简易版自动配置启动 =====\n");
        
        // 1. 加载spring.factories中的配置类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
            EnableSimpleAutoConfiguration.class, 
            this.getClass().getClassLoader());
        
        System.out.println("从spring.factories加载到 " + configurations.size() + " 个配置类:");
        for (String config : configurations) {
            System.out.println("  - " + config);
        }
        
        // 2. 过滤(简化版,实际应该检查条件注解)
        List<String> filtered = filterConfigurations(configurations);
        
        System.out.println("\n经过条件过滤后剩余 " + filtered.size() + " 个配置类\n");
        
        // 3. 返回要导入的配置类
        return filtered.toArray(new String[0]);
    }
    
    /**
     * 过滤配置类(简化版)
     */
    private List<String> filterConfigurations(List<String> configurations) {
        // 这里可以实现条件注解的判断
        // 简化版就直接返回全部
        return configurations;
    }
}

3. 自定义自动配置类

package com.example.autoconfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Hello自动配置类
 */
@Configuration
public class HelloAutoConfiguration {
    
    @Bean
    public HelloService helloService() {
        System.out.println(">>> HelloService自动配置生效");
        return new HelloService();
    }
}

/**
 * Hello服务类
 */
class HelloService {
    public String sayHello(String name) {
        return "Hello, " + name + "!";
    }
}

4. 创建spring.factories文件

位置: src/main/resources/META-INF/spring.factories

# Simple Auto Configuration
com.example.autoconfig.EnableSimpleAutoConfiguration=\
com.example.autoconfig.HelloAutoConfiguration

5. 测试类

package com.example;

import com.example.autoconfig.EnableSimpleAutoConfiguration;
import com.example.autoconfig.HelloService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * 测试自动配置
 */
@Configuration
@EnableSimpleAutoConfiguration  // ← 启用自动配置
@ComponentScan("com.example")
public class AutoConfigTest {
    
    public static void main(String[] args) {
        System.out.println("========== 测试简易版自动配置 ==========\n");
        
        // 创建容器
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext(AutoConfigTest.class);
        
        // 获取自动配置的Bean
        HelloService helloService = context.getBean(HelloService.class);
        
        // 使用
        String result = helloService.sayHello("World");
        System.out.println("\n结果:" + result);
        
        context.close();
    }
}

6. 运行结果

========== 测试简易版自动配置 ==========

===== 简易版自动配置启动 =====

从spring.factories加载到 1 个配置类:
  - com.example.autoconfig.HelloAutoConfiguration

经过条件过滤后剩余 1 个配置类

>>> HelloService自动配置生效

结果:Hello, World!

成功!


🎯 第八问:自定义Starter的自动配置实战

目标

创建一个完整的自定义Starter,包含自动配置。

功能: 短信发送服务(支持阿里云、腾讯云)


项目结构

sms-spring-boot-starter/
├── sms-spring-boot-autoconfigure/  ← 自动配置模块
│   ├── src/main/java/
│   │   └── com/example/sms/autoconfigure/
│   │       ├── SmsAutoConfiguration.java
│   │       ├── SmsProperties.java
│   │       ├── AliyunSmsService.java
│   │       └── TencentSmsService.java
│   ├── src/main/resources/
│   │   └── META-INF/
│   │       └── spring.factories
│   └── pom.xml
└── sms-spring-boot-starter/  ← Starter模块
    └── pom.xml

实现代码

1. 短信服务接口

package com.example.sms;

/**
 * 短信服务接口
 */
public interface SmsService {
    
    /**
     * 发送短信
     */
    boolean send(String phone, String message);
}

2. 阿里云实现

package com.example.sms.autoconfigure;

import com.example.sms.SmsService;

/**
 * 阿里云短信服务
 */
public class AliyunSmsService implements SmsService {
    
    private final SmsProperties properties;
    
    public AliyunSmsService(SmsProperties properties) {
        this.properties = properties;
        System.out.println(">>> 阿里云短信服务初始化");
        System.out.println("    AccessKey: " + properties.getAccessKey());
    }
    
    @Override
    public boolean send(String phone, String message) {
        System.out.println("【阿里云短信】发送到:" + phone);
        System.out.println("内容:" + message);
        return true;
    }
}

3. 腾讯云实现

package com.example.sms.autoconfigure;

import com.example.sms.SmsService;

/**
 * 腾讯云短信服务
 */
public class TencentSmsService implements SmsService {
    
    private final SmsProperties properties;
    
    public TencentSmsService(SmsProperties properties) {
        this.properties = properties;
        System.out.println(">>> 腾讯云短信服务初始化");
        System.out.println("    SecretId: " + properties.getSecretId());
    }
    
    @Override
    public boolean send(String phone, String message) {
        System.out.println("【腾讯云短信】发送到:" + phone);
        System.out.println("内容:" + message);
        return true;
    }
}

4. 配置属性类

package com.example.sms.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 短信配置属性
 */
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
    
    /**
     * 短信提供商:aliyun、tencent
     */
    private String provider = "aliyun";
    
    /**
     * 阿里云AccessKey
     */
    private String accessKey;
    
    /**
     * 阿里云SecretKey
     */
    private String secretKey;
    
    /**
     * 腾讯云SecretId
     */
    private String secretId;
    
    /**
     * 腾讯云SecretKey
     */
    private String tencentSecretKey;
    
    // Getter和Setter
    public String getProvider() {
        return provider;
    }
    
    public void setProvider(String provider) {
        this.provider = provider;
    }
    
    public String getAccessKey() {
        return accessKey;
    }
    
    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }
    
    public String getSecretKey() {
        return secretKey;
    }
    
    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }
    
    public String getSecretId() {
        return secretId;
    }
    
    public void setSecretId(String secretId) {
        this.secretId = secretId;
    }
    
    public String getTencentSecretKey() {
        return tencentSecretKey;
    }
    
    public void setTencentSecretKey(String tencentSecretKey) {
        this.tencentSecretKey = tencentSecretKey;
    }
}

5. 自动配置类

package com.example.sms.autoconfigure;

import com.example.sms.SmsService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 短信自动配置类
 */
@Configuration
@EnableConfigurationProperties(SmsProperties.class)
public class SmsAutoConfiguration {
    
    /**
     * 阿里云短信服务
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "sms", name = "provider", havingValue = "aliyun", 
        matchIfMissing = true)
    public SmsService aliyunSmsService(SmsProperties properties) {
        return new AliyunSmsService(properties);
    }
    
    /**
     * 腾讯云短信服务
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "sms", name = "provider", havingValue = "tencent")
    public SmsService tencentSmsService(SmsProperties properties) {
        return new TencentSmsService(properties);
    }
}

6. spring.factories

位置: sms-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.sms.autoconfigure.SmsAutoConfiguration

7. autoconfigure的pom.xml

<project>
    <groupId>com.example</groupId>
    <artifactId>sms-spring-boot-autoconfigure</artifactId>
    <version>1.0.0</version>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

8. starter的pom.xml

<project>
    <groupId>com.example</groupId>
    <artifactId>sms-spring-boot-starter</artifactId>
    <version>1.0.0</version>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>sms-spring-boot-autoconfigure</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
</project>

使用测试

1. 引入依赖

<dependency>
    <groupId>com.example</groupId>
    <artifactId>sms-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

2. 配置文件(阿里云)

sms:
  provider: aliyun
  access-key: your-access-key
  secret-key: your-secret-key

3. 使用

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

@RestController
public class SmsController {
    
    @Autowired
    private SmsService smsService;  // 自动注入
    
    @PostMapping("/sms/send")
    public String sendSms(@RequestParam String phone, 
                          @RequestParam String message) {
        boolean success = smsService.send(phone, message);
        return success ? "发送成功" : "发送失败";
    }
}

4. 测试结果

启动日志:

>>> 阿里云短信服务初始化
    AccessKey: your-access-key

访问接口:

POST http://localhost:8080/sms/send
phone=13800138000&message=验证码:123456

输出:

【阿里云短信】发送到:13800138000
内容:验证码:123456

5. 切换到腾讯云

修改配置:

sms:
  provider: tencent  # ← 改成腾讯云
  secret-id: your-secret-id
  tencent-secret-key: your-secret-key

重启应用,输出:

>>> 腾讯云短信服务初始化
    SecretId: your-secret-id

【腾讯云短信】发送到:13800138000
内容:验证码:123456

完美!


🐛 第九问:常见问题排查

问题1:自动配置不生效

现象:

@Autowired
private RedisTemplate redisTemplate;  // 报错:Could not autowire

排查步骤:

步骤1:检查是否引入依赖

<!-- 确认有这个依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

步骤2:检查条件注解

开启debug日志:

logging:
  level:
    org.springframework.boot.autoconfigure: DEBUG

查看日志:

Positive matches:  ← 生效的自动配置
-----------------
   RedisAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.data.redis.core.RedisOperations' (OnClassCondition)

Negative matches:  ← 不生效的自动配置
-----------------
   RabbitAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel' (OnClassCondition)

步骤3:检查是否被排除

// 检查是否手动排除了
@SpringBootApplication(exclude = {RedisAutoConfiguration.class})
public class Application {
}

// 或者配置文件
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

问题2:如何排除某个自动配置?

方式1:注解排除

@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    RedisAutoConfiguration.class
})
public class Application {
}

方式2:配置文件排除

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

问题3:如何debug自动配置?

方式1:使用Actuator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

访问: http://localhost:8080/actuator/conditions

输出:

{
  "contexts": {
    "application": {
      "positiveMatches": {
        "RedisAutoConfiguration": [
          {
            "condition": "OnClassCondition",
            "message": "@ConditionalOnClass found required class 'org.springframework.data.redis.core.RedisOperations'"
          }
        ]
      },
      "negativeMatches": {
        "RabbitAutoConfiguration": {
          "notMatched": [
            {
              "condition": "OnClassCondition",
              "message": "@ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel'"
            }
          ]
        }
      }
    }
  }
}

方式2:启动参数

java -jar app.jar --debug

方式3:配置文件

debug: true

💡 知识点总结

SpringBoot自动配置核心要点

三合一注解

  • @SpringBootConfiguration(配置类)
  • @ComponentScan(组件扫描)
  • @EnableAutoConfiguration(自动配置)

自动配置原理

  1. @EnableAutoConfiguration
  2. @Import(AutoConfigurationImportSelector.class)
  3. selectImports()方法
  4. SpringFactoriesLoader.loadFactoryNames()
  5. 读取spring.factories文件
  6. 加载133个自动配置类
  7. 条件注解过滤
  8. 注册符合条件的Bean

spring.factories文件

  • 位置:META-INF/spring.factories
  • 格式:key=value1,value2,...
  • SpringBoot会扫描所有jar包的此文件

条件注解

  • @ConditionalOnClass - 类存在
  • @ConditionalOnMissingBean - Bean不存在
  • @ConditionalOnProperty - 配置满足
  • @ConditionalOnWebApplication - Web应用

自定义Starter

  1. 创建autoconfigure模块
  2. 编写自动配置类
  3. 创建spring.factories
  4. 创建starter模块聚合依赖

记忆口诀

SpringBoot启动扫描factories,
一百多个配置类加载。
条件注解来过滤筛选,
符合条件才会生效。
用户引入依赖就能用,
无需配置开箱即来。

🤔 常见面试题

Q1: SpringBoot自动配置的原理?

A:

1. @SpringBootApplication包含@EnableAutoConfiguration
2. @EnableAutoConfiguration导入AutoConfigurationImportSelector
3. selectImports()方法通过SpringFactoriesLoader读取spring.factories
4. 加载所有自动配置类(133个)
5. 通过条件注解(@ConditionalOnXxx)过滤
6. 满足条件的配置类会注册Bean到容器
7. 实现自动配置

Q2: spring.factories文件的作用?

A:

1. SPI机制的实现
2. 存储自动配置类的全限定名
3. SpringBoot启动时会扫描所有jar包的此文件
4. 不仅用于自动配置,还用于其他扩展点

Q3: 如何自定义一个Starter?

A:

1. 创建autoconfigure模块
   - 编写自动配置类
   - 编写Properties配置类
   - 创建spring.factories

2. 创建starter模块
   - 聚合依赖

3. 打包发布

4. 使用方引入依赖即可

Q4: @ConditionalOnMissingBean的作用?

A:

当容器中不存在指定Bean时,才会创建。

作用:
1. 提供默认实现
2. 允许用户自定义覆盖
3. 避免Bean重复创建

示例:
@Bean
@ConditionalOnMissingBean
public RedisTemplate redisTemplate() {
    // 用户没自定义,才创建默认的
}

💬 写在最后

@EnableAutoConfigurationspring.factories,我们深入剖析了SpringBoot自动配置的完整原理:

  • 🔍 理解了三合一注解
  • 📊 掌握了自动配置加载流程
  • 💻 手写了简易版自动配置
  • 🎯 实战了自定义Starter

这篇万字长文,希望能让你彻底搞懂SpringBoot自动配置的魔法!

如果这篇文章对你有帮助,请:

  • 👍 点赞支持
  • ⭐ 收藏备用
  • 🔄 转发分享
  • 💬 评论交流

感谢阅读,期待下次再见! 👋