Skip to content

yangbb2288/createSpringBoot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

手写一个简易 Spring Boot 脚手架

从零搭建,理解 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 容器在加载配置类时会执行 ImportSelectorselectImports() 方法,返回需要注册的类名数组。

第三步:读取配置文件(Spring Boot 3 方式)

这是整个脚手架的核心。我们实现了与 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.WebServerAutoConfiguration

Spring Boot 2 vs Spring Boot 3 的变化:

版本 配置文件 读取方式
Boot 2 META-INF/spring.factories SpringFactoriesLoader.loadFactoryNames()
Boot 3 META-INF/spring/*.imports 直接 ClassLoader.getResources() + 逐行读取

.imports 文件更简洁(每行一个类名),性能更好,避免了 Spring Boot 2 中 spring.factories 的 key-value 解析开销。

第四步:条件过滤(@ConditionalOnClass)

并不是所有自动配置类都会被加载。比如项目中同时有 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
        }
    }
}

第五步:自动配置类注册 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 中做了校验)。

第六步:启动内嵌 Web 服务器

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

如何扩展这个脚手架

  1. 添加新的自动配置类:编写 @Configuration 类,在 AutoConfiguration.imports 中加上全限定名。
  2. 添加自定义条件注解:模仿 @RjzmsqhConditionalOnClass,实现 Condition 接口,结合 @Conditional 使用。
  3. 支持更多内嵌容器:实现 WebServer 接口(如 Undertow),在 WebServerAutoConfiguration 中添加对应的 @Bean 和条件注解。
  4. 支持 application.properties:通过 Spring 的 Environment 抽象读取配置文件,使自动配置可配置化。

小结

这个简易脚手架不到 300 行代码,但涵盖了 Spring Boot 最核心的几个机制:

机制 对应代码
组合注解 @RjzmsqhSpringBootApplication
自动配置入口 @RjzmsqhEnableAutoConfiguration + @Import
配置类发现 AutoConfigurationImportSelector + .imports 文件
条件过滤 @RjzmsqhConditionalOnClass + Condition
自动配置类 WebServerAutoConfiguration
启动器 RjzmsqhSpringApplication

理解这些核心原理后,再看官方的 spring-boot-autoconfigure 源码,思路会清晰很多。

我有自己博客,欢迎大家访问:https://rjzmsqh.top

About

这是一个手写springboot简单框架,包括自动配置,实现web容器内嵌

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages