💥 开场:一次"见鬼"的经历
时间: 周一早上
地点: 办公室
事件: 新项目启动
我: "今天要集成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: 6379
↓
7. 绑定到RedisProperties
↓
8. 注入到ConnectionFactory
↓
9. 自动配置完成!
🔧 第七问:手写简易版自动配置
目标
实现一个最简单的自动配置机制,支持:
- ✅ 读取spring.factories文件
- ✅ 加载自动配置类
- ✅ 简单的条件注解判断
- ✅ 注册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(自动配置)
✅ 自动配置原理
- @EnableAutoConfiguration
- @Import(AutoConfigurationImportSelector.class)
- selectImports()方法
- SpringFactoriesLoader.loadFactoryNames()
- 读取spring.factories文件
- 加载133个自动配置类
- 条件注解过滤
- 注册符合条件的Bean
✅ spring.factories文件
- 位置:META-INF/spring.factories
- 格式:key=value1,value2,...
- SpringBoot会扫描所有jar包的此文件
✅ 条件注解
- @ConditionalOnClass - 类存在
- @ConditionalOnMissingBean - Bean不存在
- @ConditionalOnProperty - 配置满足
- @ConditionalOnWebApplication - Web应用
✅ 自定义Starter
- 创建autoconfigure模块
- 编写自动配置类
- 创建spring.factories
- 创建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() {
// 用户没自定义,才创建默认的
}
💬 写在最后
从@EnableAutoConfiguration到spring.factories,我们深入剖析了SpringBoot自动配置的完整原理:
- 🔍 理解了三合一注解
- 📊 掌握了自动配置加载流程
- 💻 手写了简易版自动配置
- 🎯 实战了自定义Starter
这篇万字长文,希望能让你彻底搞懂SpringBoot自动配置的魔法!
如果这篇文章对你有帮助,请:
- 👍 点赞支持
- ⭐ 收藏备用
- 🔄 转发分享
- 💬 评论交流
感谢阅读,期待下次再见! 👋