Spring Boot 最核心的能力是什么?大多数人会说是"约定优于配置"。但如果你翻过自动配置的源码,会发现这背后的设计远不止"约定"两个字那么简单。
自动配置本质上是在解决一个问题:在不确定运行环境的情况下,安全地创建合适的 Bean。这个问题涉及三个子问题------"要不要创建"、"创建什么"、"创建后通知谁",分别对应三种设计模式。
条件模式:Spring Boot 的"要不要创建"决策
Spring Boot 没有一个叫"条件模式"的 GoF 模式,但 @Conditional 系列注解就是条件模式的标准实现。它的本质是:把创建决策从代码逻辑变成声明式规则。
java
// 自动配置类的典型写法
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // classpath 上有 DataSource 才生效
@ConditionalOnMissingBean(DataSource.class) // 容器里没有 DataSource 才创建
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(DataSourceProperties properties) {
return DataSourceBuilder.create().build();
}
}
这种写法跟你在业务代码里写的 if (xxx != null) 看似一样,但有一个根本区别:条件的评估时机不同。
if-else 在运行时评估,每次调用都要判断。@Conditional 在容器启动时评估一次,评估结果决定了 Bean 是否注册,之后不再判断。这意味着条件模式把"分支决策"从方法级提升到了容器级。
我在一个项目里看到有人这么写:
java
@Service
public class PaymentService {
@Autowired
private AlipayClient alipayClient; // 可能不存在
@Autowired
private WechatPayClient wechatPayClient; // 可能不存在
public void pay(String channel, Order order) {
if ("alipay".equals(channel)) {
alipayClient.pay(order); // NPE 风险
} else if ("wechat".equals(channel)) {
wechatPayClient.pay(order); // NPE 风险
}
}
}
问题在于:用运行时 if-else 来做 Bean 存在性判断,跟自动配置的思路完全相反。正确的做法是用 @ConditionalOnProperty 或 @ConditionalOnBean,让不存在的支付渠道在启动时就排除。
但这里有个陷阱:@ConditionalOnMissingBean 的评估顺序跟 @Configuration 类的加载顺序有关。如果你在自定义 Configuration 里手动声明了一个 DataSource,但加载顺序在自动配置之后,那自动配置可能已经先创建了默认的 DataSource。这是很多人遇到的"自动配置不生效"的根源。
工厂模式:SpringFactoriesLoader → AutoConfiguration
Spring Boot 2.x 用 SpringFactoriesLoader,3.x 用 ImportSelect + META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,但本质都是同一个模式:配置驱动的工厂。
java
// SpringFactoriesLoader 的核心逻辑
public final class SpringFactoriesLoader {
public static <T> List<T> loadFactories(Class<T> factoryType, ClassLoader classLoader) {
// 1. 读 META-INF/spring.factories
// 2. 找 factoryType 对应的实现类名
// 3. 反射创建实例
List<String> factoryNames = loadFactoryNames(factoryType, classLoader);
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryType, classLoader));
}
return result;
}
}
这个设计的精妙之处不在工厂本身,而在配置文件作为产品目录 。传统的工厂模式是代码里硬编码产品列表,Spring Boot 把产品列表放到了 spring.factories 文件里。这意味着:
- 加一个自动配置类,不需要改任何已有代码,只需要在
spring.factories里加一行 - 去掉一个自动配置类,在
application.yml里spring.autoconfigure.exclude就行 - starter 包的"引入即生效",本质上就是往
spring.factories里注册了新的工厂产品
我在一个项目里看到有人自定义了一个 starter,但忘了在 spring.factories 里注册,结果自动配置根本不生效。debug 了半天才发现不是条件不满足,而是工厂压根不知道这个配置类的存在。
这种 bug 的特点:不报错,只是静默不生效。比报错更难排查。
观察者模式:自动配置后的事件通知
自动配置完成后,很多场景需要做初始化动作------建表、灌数据、检查连接。Spring Boot 用 ApplicationEvent 机制来做这件事,这就是观察者模式。
java
// 监听容器启动完成事件
@Component
public class DataInitializer implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 所有 Bean 都创建完了,可以安全地做初始化
initDatabase();
}
}
// 或者用 @EventListener(更现代的写法)
@Component
public class CacheWarmer {
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
// 应用完全就绪,开始预热缓存
cacheManager.preload();
}
}
关键区别:ContextRefreshedEvent 在 Bean 创建完后触发,ApplicationReadyEvent 在所有初始化(包括自动配置的后处理)完成后触发。如果你在 ContextRefreshedEvent 里访问自动配置创建的 Bean,可能还没完全初始化。
踩过一个坑:在 @PostConstruct 里访问自动配置的 RedisTemplate,结果拿到 null。原因是 @PostConstruct 在当前 Bean 初始化时触发,但自动配置的 Bean 可能还没创建。改用 @EventListener(ApplicationReadyEvent.class) 后问题解决。
这个问题的本质:观察者模式的时序很关键。同样的模式,触发时机差一步就是 null 和正常对象的区别。
三种模式的协作关系
把这三个模式放在一起看,Spring Boot 自动配置的架构就很清晰了:
- 工厂模式 负责"从哪找配置类"------通过
spring.factories或 imports 文件发现 - 条件模式 负责"要不要创建"------通过
@Conditional系列注解过滤 - 观察者模式负责"创建后做什么"------通过 ApplicationEvent 通知
三个模式各管一个阶段,互不干扰,但协作完成了一个完整的"发现→过滤→生效→通知"流程。这种分层设计比在一个类里 if-else 搞定所有逻辑要灵活得多。
但灵活是有代价的。自动配置最让人头疼的就是排查困难 ------一个 Bean 为什么没创建?是条件不满足?是工厂没发现?还是加载顺序问题?Spring Boot 提供了 --debug 和 /actuator/conditions 端点来帮助排查,但说实话,第一次遇到的时候你大概率还是得翻源码。
一些实践建议
-
自定义自动配置时,条件越具体越好 。
@ConditionalOnClass+@ConditionalOnMissingBean+@ConditionalOnProperty三件套一起用,避免"不该创建的 Bean 被创建了"。 -
@ConditionalOnMissingBean的参数要精确 。不要写@ConditionalOnMissingBean不带参数,这会导致任何同类型的 Bean 都阻止自动配置。用value或name精确指定。 -
自动配置类不要做重逻辑 。自动配置的职责是"创建和装配",不是"执行业务"。重逻辑放到
@EventListener或@PostConstruct里。 -
排查自动配置问题的第一步永远是
/actuator/conditions。别上来就翻源码,先看条件评估报告。
我在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」,23 个设计模式用漫画 + 答题的方式讲,感兴趣可以搜一下看看。