从零搭建,理解 Spring Boot 自动配置的核心原理
Spring Boot 最核心的能力是什么?自动配置。
用过 Spring Boot 的人都知道,只需一个 @SpringBootApplication 注解,内嵌 Tomcat、DispatcherServlet、数据源等就能自动生效。这背后到底是怎么做到的?
本文通过手写一个极简的 Spring Boot 脚手架(RjzmsqhSpringBoot),一步步还原自动配置的核心流程。
完整代码:https://github.com/yangbb2288/createSpringBoot
createSpringBoot
├── pom.xml # 父 POM(聚合多模块)
├── RjzmsqhSpringBoot # 自定义 Spring Boot 核心模块
│ ├── pom.xml
│ └── src/main/java/springboot/
│ ├── RjzmsqhSpringBootApplication.java # 组合注解
│ ├── RjzmsqhSpringApplication.java # 启动器
│ ├── configuration/
│ │ ├── RjzmsqhEnableAutoConfiguration.java # 启用自动配置注解
│ │ └── AutoConfigurationImportSelector.java # 核心:读取导入文件
│ ├── conditional/
│ │ ├── RjzmsqhConditionalOnClass.java # 条件注解
│ │ └── RjzmsqhClassOnCondition.java # 条件判断逻辑
│ └── webServer/
│ ├── WebServer.java # Web 服务器接口
│ ├── WebServerAutoConfiguration.java # 自动配置类
│ └── impl/
│ ├── TomcatWebServer.java # Tomcat 实现
│ └── JettyWebServer.java # Jetty 实现
└── Test # 测试工程
└── src/main/java/top/rjzmsqh/
├── TestApplication.java
└── controller/UserController.java
整体流程如下:
@RjzmsqhSpringBootApplication
└─ @RjzmsqhEnableAutoConfiguration
└─ @Import(AutoConfigurationImportSelector.class)
└─ 读取 META-INF/spring/*.imports 文件
└─ 获取自动配置类全限定名列表
└─ 按条件注解过滤(@ConditionalOnClass)
└─ 注册 Bean
Spring Boot 最常用的 @SpringBootApplication 其实是一个组合注解。我们模仿它实现 @RjzmsqhSpringBootApplication:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ComponentScan
@RjzmsqhEnableAutoConfiguration
public @interface RjzmsqhSpringBootApplication {
}@ComponentScan:扫描当前包及其子包的 @Component、@Controller、@Service 等。
@RjzmsqhEnableAutoConfiguration:启用自动配置,这是关键入口。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AutoConfigurationImportSelector.class)
public @interface RjzmsqhEnableAutoConfiguration {
}核心是 @Import(AutoConfigurationImportSelector.class),Spring 容器在加载配置类时会执行 ImportSelector 的 selectImports() 方法,返回需要注册的类名数组。
这是整个脚手架的核心。我们实现了与 Spring Boot 3 一致的 .imports 文件读取机制:
public class AutoConfigurationImportSelector implements DeferredImportSelector {
private static final String PATH =
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> configurations = getCandidateConfigurations();
configurations = filter(configurations);
return configurations.toArray(new String[0]);
}
private List<String> getCandidateConfigurations() {
List<String> configurations = new ArrayList<>();
// 通过 ClassLoader 加载 classpath 下所有匹配的 .imports 文件
Enumeration<URL> urls = getBeanClassLoader().getResources(PATH);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(url.openStream()))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue; // 跳过空行和注释
}
configurations.add(line);
}
}
}
return configurations;
}
}对应的配置文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
# Auto Configuration Imports for Rjzmsqh Spring Boot
springboot.webServer.WebServerAutoConfigurationSpring Boot 2 vs Spring Boot 3 的变化:
版本 配置文件 读取方式 Boot 2 META-INF/spring.factoriesSpringFactoriesLoader.loadFactoryNames()Boot 3 META-INF/spring/*.imports直接 ClassLoader.getResources()+ 逐行读取
.imports文件更简洁(每行一个类名),性能更好,避免了 Spring Boot 2 中spring.factories的 key-value 解析开销。
并不是所有自动配置类都会被加载。比如项目中同时有 Tomcat 和 Jetty 的依赖,但实际运行时只会有一个生效。这就是 条件注解 的作用。
自定义 @RjzmsqhConditionalOnClass 注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(RjzmsqhClassOnCondition.class)
public @interface RjzmsqhConditionalOnClass {
String value(); // 要检查的类名
}对应的条件判断逻辑:
public class RjzmsqhClassOnCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attrs =
metadata.getAnnotationAttributes(RjzmsqhConditionalOnClass.class.getName());
String className = (String) attrs.get("value");
try {
context.getClassLoader().loadClass(className);
return true; // 类存在 → 条件满足
} catch (ClassNotFoundException e) {
return false; // 类不存在 → 条件不满足,不注册 Bean
}
}
}自动配置类 WebServerAutoConfiguration 根据 classpath 中的依赖,决定创建哪个 Web 服务器:
@Configuration
public class WebServerAutoConfiguration {
@Bean
@RjzmsqhConditionalOnClass("org.apache.catalina.startup.Tomcat")
public TomcatWebServer tomcatWebServer(AnnotationConfigWebApplicationContext applicationContext) {
return new TomcatWebServer(applicationContext);
}
@Bean
@RjzmsqhConditionalOnClass("org.eclipse.jetty.server.Server")
public JettyWebServer jettyWebServer(AnnotationConfigWebApplicationContext applicationContext) {
return new JettyWebServer(applicationContext);
}
}如果 classpath 中有 Tomcat,就创建 TomcatWebServer;如果有 Jetty,就创建 JettyWebServer。两个同时存在就报错(RjzmsqhSpringApplication 中做了校验)。
Tomcat 实现:内嵌 Tomcat,注册 Spring MVC 的 DispatcherServlet。
public class TomcatWebServer implements WebServer {
private final AnnotationConfigWebApplicationContext webApplicationContext;
public TomcatWebServer(AnnotationConfigWebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
@Override
public void start() {
Tomcat tomcat = new Tomcat();
// 配置端口和连接器
Connector connector = new Connector();
connector.setPort(8080);
Service service = tomcat.getServer().findService("Tomcat");
service.addConnector(connector);
// 配置 Engine / Host / Context
Engine engine = new StandardEngine();
engine.setDefaultHost("localhost");
Host host = new StandardHost();
host.setName("localhost");
Context context = new StandardContext();
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener());
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
// 注册 DispatcherServlet
tomcat.addServlet("", "dispatcher",
new DispatcherServlet(webApplicationContext));
context.addServletMappingDecoded("/*", "dispatcher");
tomcat.start();
}
}RjzmsqhSpringApplication.run() 是整个应用的入口:
public class RjzmsqhSpringApplication {
public static void run(Class<?> primarySource, String[] args) {
// 1. 创建 Spring 容器
AnnotationConfigWebApplicationContext context =
new AnnotationConfigWebApplicationContext();
context.register(primarySource);
context.refresh(); // 触发自动配置加载
// 2. 启动 Web 服务器
WebServer webServer = getWebServer(context);
webServer.start();
}
private static WebServer getWebServer(ApplicationContext context) {
Map<String, WebServer> beans = context.getBeansOfType(WebServer.class);
if (beans.isEmpty()) {
throw new IllegalStateException("没有找到可用的 Web 服务器");
}
if (beans.size() > 1) {
throw new IllegalStateException("找到多个 Web 服务器,请排除多余的依赖");
}
return beans.values().stream().findFirst().get();
}
}@RzjmsqhSpringBootApplication
public class TestApplication {
public static void main(String[] args) {
RjzmsqhSpringApplication.run(TestApplication.class, args);
}
}启动后访问 http://localhost:8080/test,就能看到 UserController 返回的响应。
TestApplication.main()
└─ RjzmsqhSpringApplication.run(TestApplication.class, args)
├─ 创建 AnnotationConfigWebApplicationContext
├─ context.register(TestApplication.class)
├─ context.refresh()
│ └─ 处理 @ComponentScan → 扫描 UserController
│ └─ 处理 @Import(AutoConfigurationImportSelector.class)
│ └─ selectImports()
│ └─ 读取 classpath 下所有 *.imports 文件
│ └─ 得到 WebServerAutoConfiguration
│ └─ 解析 @Bean 方法
│ └─ 检查 @RjzmsqhConditionalOnClass
│ ├─ Tomcat 存在 → 创建 TomcatWebServer
│ └─ Jetty 存在 → 创建 JettyWebServer
└─ getWebServer(context)
└─ webServer.start()
└─ 内嵌 Tomcat/Jetty 启动,注册 DispatcherServlet
- 添加新的自动配置类:编写
@Configuration类,在AutoConfiguration.imports中加上全限定名。 - 添加自定义条件注解:模仿
@RjzmsqhConditionalOnClass,实现Condition接口,结合@Conditional使用。 - 支持更多内嵌容器:实现
WebServer接口(如 Undertow),在WebServerAutoConfiguration中添加对应的@Bean和条件注解。 - 支持 application.properties:通过 Spring 的
Environment抽象读取配置文件,使自动配置可配置化。
这个简易脚手架不到 300 行代码,但涵盖了 Spring Boot 最核心的几个机制:
| 机制 | 对应代码 |
|---|---|
| 组合注解 | @RjzmsqhSpringBootApplication |
| 自动配置入口 | @RjzmsqhEnableAutoConfiguration + @Import |
| 配置类发现 | AutoConfigurationImportSelector + .imports 文件 |
| 条件过滤 | @RjzmsqhConditionalOnClass + Condition |
| 自动配置类 | WebServerAutoConfiguration |
| 启动器 | RjzmsqhSpringApplication |
理解这些核心原理后,再看官方的 spring-boot-autoconfigure 源码,思路会清晰很多。
我有自己博客,欢迎大家访问:https://rjzmsqh.top