SpringBoot 为什么知道该创建哪些 Bean?自动配置真正的核心是什么

刚学 SpringBoot 的时候,我一直有个疑问。

项目里明明没有写:

@Bean
public RedisTemplate redisTemplate() {

}

也没有:

new RedisTemplate()

但是代码里却可以直接注入:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

甚至连配置类都不用写。

启动项目就能用。


MyBatis 也是一样。

引入依赖:

mybatis-spring-boot-starter

然后写一个 Mapper:

@Mapper
public interface UserMapper {

}

直接就能访问数据库。


这些年大家已经习惯了 SpringBoot 的这种体验。

但仔细想想。

这里其实有一个很关键的问题。


Spring 为什么知道要创建这些 Bean?

很多人的第一反应是:

SpringBoot 自动创建的。

这句话其实不准确。

因为 SpringBoot 根本不知道 Redis 是什么。

也不知道 MyBatis 是什么。

更不知道你项目里到底需要哪些组件。


假设我自己写一个类:

public class PxpClient {

}

然后打成 Jar 包。

引入项目。

SpringBoot 会自动帮我创建 Bean 吗?

答案显然不会。


这说明一件事。

SpringBoot 不会凭空创建 Bean。

一定有人提前告诉了 SpringBoot:

这里有个配置类。

这里有个 Bean。

请帮我加载。

那么问题来了。

到底是谁告诉 SpringBoot 的?


从启动类开始找答案

项目启动入口:

@SpringBootApplication
public class Application {

}

上一篇我们已经分析过。

@SpringBootApplication

最终会进入:

@EnableAutoConfiguration

而:

@EnableAutoConfiguration

又会导入:

@Import(AutoConfigurationImportSelector.class)

看到这里。

答案已经开始浮出水面。


流程如下:

@SpringBootApplication
            ↓
@EnableAutoConfiguration
            ↓
AutoConfigurationImportSelector

SpringBoot 自动配置的核心入口出现了。


AutoConfigurationImportSelector 在干什么?

继续 Debug。

进入:

selectImports()

调用链如下:

selectImports()
        ↓
getAutoConfigurationEntry()
        ↓
getCandidateConfigurations()

最终进入:

getCandidateConfigurations()

这里就是 SpringBoot 自动配置的关键。


原来 SpringBoot 根本没有扫描 Jar

刚开始学习 SpringBoot 的时候。

我一直以为:

SpringBoot 会扫描所有 Jar 包。

然后找到自动配置类。

结果 Debug 到这里才发现。

根本不是。


SpringBoot 干的事情非常简单。

它只是读取了一份名单。

SpringBoot 2.x:

META-INF/spring.factories

SpringBoot 3.x:

META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports

打开这个文件。

你会看到类似内容:

org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

看到这里。

很多同学第一次都会有一种恍然大悟的感觉。


原来 SpringBoot 根本没有猜。

也没有扫描。

而是:

提前登记
提前备案
按名单加载

流程变成:

引入 Starter
       ↓
获得自动配置名单
       ↓
读取配置类
       ↓
导入 Spring 容器
       ↓
创建 Bean

RedisTemplate 到底在哪创建?

继续进入:

RedisAutoConfiguration

很快就能看到:

@Bean
public RedisTemplate<Object, Object> redisTemplate(
        RedisConnectionFactory connectionFactory) {

}

到这里真相大白。


原来:

RedisTemplate

根本不是 SpringBoot 凭空创造的。

而是:

RedisAutoConfiguration

提前写好了创建逻辑。

SpringBoot 做的事情只是:

找到配置类

导入配置类

交给 Spring 创建 Bean

那为什么有些自动配置会生效,有些不会?

看到这里。

新的问题又来了。


既然 SpringBoot 已经找到了:

RedisAutoConfiguration

DataSourceAutoConfiguration

WebMvcAutoConfiguration

这些自动配置类。

是不是都会加载?

答案是:

不会。

很多人觉得:

SpringBoot 最厉害的是自动配置。

其实只说对了一半。

更准确地说应该是:

SpringBoot 最厉害的是有条件地自动配置。

例如 Redis 自动配置类中。

经常能看到类似代码:

@Configuration
@ConditionalOnClass(RedisOperations.class)
public class RedisAutoConfiguration {

}

意思很简单:

只有 Redis 相关类存在,
这个配置类才允许加载。

如果项目根本没有引入 Redis 依赖。

那么:

RedisAutoConfiguration

直接跳过。


再比如:

@Bean
@ConditionalOnMissingBean
public RedisTemplate<Object, Object> redisTemplate() {

}

这里又多了一个条件:

容器中不存在 RedisTemplate,
才创建 RedisTemplate。

如果你自己写了:

@Bean
public RedisTemplate redisTemplate() {

}

SpringBoot 默认配置就不会生效。


看到这里。

很多以前觉得奇怪的问题就解释通了。


为什么引入 Starter 有时候生效?

因为条件满足。


为什么有时候不生效?

因为条件不满足。


为什么自己写的 Bean 能覆盖 SpringBoot 默认配置?

因为:

@ConditionalOnMissingBean

发现容器里已经存在同类型 Bean。

于是放弃创建。


所以 SpringBoot 的完整流程其实是:

引入 Starter
        ↓
读取 AutoConfiguration.imports
        ↓
找到自动配置类
        ↓
检查条件
        ↓
满足条件
        ↓
创建 Bean

Starter 的本质到底是什么?

很多人觉得:

Starter 就是依赖包。

其实不准确。

更准确地说:

Starter = 依赖集合 + 自动配置入口

里面不仅有代码。

还有:

自动配置类名单

SpringBoot 正是通过这份名单。

知道应该加载哪些配置。

再通过条件装配。

决定哪些配置最终生效。


总结

为什么引入一个 Starter。

项目里就突然多出了一堆 Bean?

答案其实并不复杂。


Starter 提供:

自动配置类

SpringBoot 负责:

发现自动配置类

条件装配负责:

判断是否允许加载

Spring 负责:

创建 Bean

整个流程如下:

Starter
      ↓
AutoConfiguration.imports
      ↓
AutoConfiguration
      ↓
Conditional
      ↓
Bean

这就是 SpringBoot 自动配置体系最核心的一条链路。


上一篇:

《一个注解启动整个项目?@SpringBootApplication 到底做了什么?》

下一篇:

《Spring MVC 为什么只写一个注解就能接收请求?》


评论区聊聊:

你有没有遇到过自动配置明明存在,但就是没有生效的情况?当时你是怎么排查出来的?

更多推荐