【SpringBoot合集-02】Spring Boot 核心流程学习
目录一、手写模拟 Spring Boot 启动过程1. 核心思想铺垫2. 手写实现从注解到容器启动第一步自定义启动注解第二步包扫描注解定义第三步自定义 Bean 标记注解第四步手写简化版 Spring 容器第五步手写启动入口类3. 原理与踩坑说明二、手写模拟 Spring Boot 条件注解功能1. 条件注解的核心作用2. 核心执行时机3. 手写实现 MyConditionalOnClass第一步定义条件元注解第二步定义条件匹配接口第三步实现类存在条件处理器第四步定义业务条件注解4. 集成到包扫描流程5. 扩展ConditionalOnMissingBean 的实现逻辑三、自动配置功能与 spring.factories 文件解析1. 自动配置的本质2. spring.factories 文件解析原理手写配置文件手写文件加载工具类3. 手写自动配置导入逻辑第一步定义 Import 与 ImportSelector第二步实现自动配置选择器第三步定义自动配置开启注解第四步增强启动注解4. 自动配置类示例5. 常见踩坑四、Spring Boot 整合 Tomcat 底层源码分析1. 内嵌 Tomcat 的核心思想2. 手写内嵌 Tomcat 启动第一步引入依赖第二步封装 TomcatWebServer3. 集成到启动流程4. 源码级细节说明五、自动配置类加载全流程源码梳理1. 完整执行链路图2. 关键扩展机制3. 用户配置优先的实现六、面试速记总结一、手写模拟 Spring Boot 启动过程1. 核心思想铺垫Spring Boot 不是一套全新的框架它本质是对 Spring 容器的 “封装增强”核心解决 Spring 原生开发中配置繁琐、部署依赖外部容器的痛点。它的所有能力最终都落地到「Bean 的扫描、注册、实例化」这套 Spring 核心流程上。启动流程的本质可以浓缩为三步创建 Spring 容器上下文解析启动类完成包扫描与 Bean 定义注册刷新容器实例化 Bean若是 Web 环境则启动内嵌服务器2. 手写实现从注解到容器启动第一步自定义启动注解模拟原生SpringBootApplication先实现最基础的包扫描能力后续再叠加自动配置。/** * 自定义启动注解对标SpringBootApplication * 组合包扫描注解后续会叠加自动配置能力 */ Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented MyComponentScan // 引入自定义包扫描注解 public interface MySpringBootApplication { }第二步包扫描注解定义对应 Spring 的ComponentScan用于指定扫描路径默认取启动类所在包。Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) public interface MyComponentScan { // 指定扫描包路径为空则默认扫描启动类所在包 String value() default ; }第三步自定义 Bean 标记注解对标Component标记哪些类需要被 Spring 容器管理。Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) public interface MyComponent { }第四步手写简化版 Spring 容器核心实现「注册配置类 → 包扫描 → Bean 定义注册 → 实例化」的完整流程。/** * 简化版Spring注解容器对标AnnotationConfigApplicationContext */ public class MyAnnotationConfigApplicationContext { // Bean定义池存储所有Bean的Class信息对应Spring的BeanDefinitionMap private final MapString, Class? beanDefinitionMap new ConcurrentHashMap(); // 单例Bean缓存存储实例化后的单例对象对应Spring的一级缓存 private final MapString, Object singletonObjects new ConcurrentHashMap(); /** * 注册配置类启动类作为扫描的入口 */ public void register(Class? configClass) { String beanName Introspector.decapitalize(configClass.getSimpleName()); beanDefinitionMap.put(beanName, configClass); } /** * 刷新容器Spring最核心的方法完成所有Bean的初始化 */ public void refresh() { // 1. 执行包扫描把所有MyComponent的类注册为Bean定义 scanComponentPackages(); // 2. 实例化所有单例Bean简化版暂不处理依赖注入 instantiateSingletonBeans(); } /** * 包扫描核心逻辑 * 读取启动类的包路径遍历类文件识别注解注册Bean定义 */ private void scanComponentPackages() { // 获取启动配置类 Class? startupClass beanDefinitionMap.values().iterator().next(); MyComponentScan scanAnnotation startupClass.getAnnotation(MyComponentScan.class); // 没指定路径就用启动类所在包这就是Spring Boot的约定 String scanPackage scanAnnotation.value(); if (scanPackage.isEmpty()) { scanPackage startupClass.getPackage().getName(); } // 把包名转为类路径读取所有class文件 String classPath scanPackage.replace(., /); URL resource startupClass.getClassLoader().getResource(classPath); if (resource null) return; File packageDir new File(resource.getFile()); // 遍历包下所有class文件 File[] classFiles packageDir.listFiles(f - f.getName().endsWith(.class)); if (classFiles null) return; for (File file : classFiles) { String className scanPackage . file.getName().replace(.class, ); try { Class? clazz Class.forName(className); // 只有加了MyComponent的类才注册进容器 if (clazz.isAnnotationPresent(MyComponent.class)) { String beanName Introspector.decapitalize(clazz.getSimpleName()); beanDefinitionMap.put(beanName, clazz); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /** * 实例化所有单例Bean * 真实Spring中这一步会处理依赖注入、循环依赖、BeanPostProcessor等扩展 */ private void instantiateSingletonBeans() { for (Map.EntryString, Class? entry : beanDefinitionMap.entrySet()) { String beanName entry.getKey(); Class? beanClass entry.getValue(); try { Object bean beanClass.getDeclaredConstructor().newInstance(); singletonObjects.put(beanName, bean); } catch (Exception e) { e.printStackTrace(); } } } /** * 从容器获取Bean */ public Object getBean(String beanName) { return singletonObjects.get(beanName); } public boolean containsBean(String beanName) { return singletonObjects.containsKey(beanName); } }第五步手写启动入口类对标SpringApplication.run()封装启动流程public class MySpringApplication { public static MyAnnotationConfigApplicationContext run(Class? primarySource) { // 1. 创建容器上下文 MyAnnotationConfigApplicationContext context new MyAnnotationConfigApplicationContext(); // 2. 注册启动类 context.register(primarySource); // 3. 刷新容器完成所有Bean初始化 context.refresh(); System.out.println(MySpringBoot 容器启动完成); return context; } }启动测试// 启动类 MySpringBootApplication public class BootDemoApplication { public static void main(String[] args) { MyAnnotationConfigApplicationContext context MySpringApplication.run(BootDemoApplication.class); // 测试从容器获取Bean UserService userService (UserService) context.getBean(userService); userService.sayHello(); } } // 业务Bean MyComponent public class UserService { public void sayHello() { System.out.println(hello, my spring boot!); } }3. 原理与踩坑说明为什么要先存 BeanDefinition 再实例化因为 Spring 需要先收集全所有 Bean 的元信息才能处理依赖注入、Bean 的先后顺序、后置处理器扩展。如果边扫描边实例化会出现依赖的 Bean 还没扫描到的问题。手写版的局限性只支持目录形式的类路径不支持 Jar 包内的扫描真实 Spring 用ResourcePatternResolver统一处理各种资源形式。约定大于配置的体现默认扫描启动类所在包不用我们手动写包路径这就是 Spring Boot 最基础的 约定。二、手写模拟 Spring Boot 条件注解功能1. 条件注解的核心作用条件注解是自动配置的底层基石只有满足预设条件时对应的配置类或 Bean 才会被注册进容器。Spring 原生提供Conditional元注解 Condition匹配接口Spring Boot 在此基础上扩展了大量常用条件ConditionalOnClass类存在生效、ConditionalOnMissingBean容器无该 Bean 生效等2. 核心执行时机条件判断发生在BeanDefinition 注册阶段不满足条件的类连 Bean 定义都不会进入容器更不会被实例化不会占用资源。3. 手写实现 MyConditionalOnClass第一步定义条件元注解对标 Spring 的Conditional用于指定条件处理器。Target({ElementType.TYPE, ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) public interface MyConditional { // 指定条件处理器的Class Class? extends MyCondition value(); }第二步定义条件匹配接口public interface MyCondition { /** * 条件匹配方法 * return true表示满足条件Bean可以注册 */ boolean matches(Class? targetClass); }第三步实现类存在条件处理器判断类路径下是否存在指定的类存在则满足条件。public class OnClassCondition implements MyCondition { Override public boolean matches(Class? targetClass) { MyConditionalOnClass annotation targetClass.getAnnotation(MyConditionalOnClass.class); if (annotation null) { return true; // 没加条件注解默认放行 } String targetClassName annotation.value(); try { // 反射加载类加载成功说明类路径下存在 Class.forName(targetClassName); return true; } catch (ClassNotFoundException e) { // 类不存在不满足条件 return false; } } }第四步定义业务条件注解Target({ElementType.TYPE, ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) MyConditional(OnClassCondition.class) // 绑定条件处理器 public interface MyConditionalOnClass { // 用字符串存类名而不是Class类型 // 原因如果类不存在写Class类型编译就会报错用字符串可以安全反射判断 String value(); }4. 集成到包扫描流程在容器的scanComponentPackages方法中注册 Bean 前增加条件校验// 识别MyComponent注解 if (clazz.isAnnotationPresent(MyComponent.class)) { // 执行条件判断满足条件才注册 if (matchCondition(clazz)) { String beanName Introspector.decapitalize(clazz.getSimpleName()); beanDefinitionMap.put(beanName, clazz); } }补充条件判断工具方法private boolean matchCondition(Class? clazz) { MyConditional conditional clazz.getAnnotation(MyConditional.class); if (conditional null) { return true; } try { // 实例化条件处理器执行匹配逻辑 MyCondition condition conditional.value().getDeclaredConstructor().newInstance(); return condition.matches(clazz); } catch (Exception e) { e.printStackTrace(); return false; } }5. 扩展ConditionalOnMissingBean 的实现逻辑这个注解是「用户配置优先」的核心容器中不存在指定 Bean 时自动配置的 Bean 才会生效。执行时机必须在所有 Bean 定义都注册完成后、实例化前执行常见坑点如果配置类加载顺序不对会出现判断时用户 Bean 还没注册导致自动配置错误生效。Spring Boot 通过AutoConfigureOrder强制排序来规避这个问题。三、自动配置功能与 spring.factories 文件解析1. 自动配置的本质自动配置不是黑魔法核心逻辑非常清晰提前写好一堆配置类比如 Tomcat 配置、数据源配置启动时通过配置文件批量发现这些配置类配合条件注解筛选出适配当前环境的配置类把这些配置类自动注册进容器创建默认的 Bean2. spring.factories 文件解析原理这是 Spring Boot 扩展机制的核心属于 SPI服务发现接口思想的实现。所有 Jar 包都可以在META-INF/spring.factories中配置自己的自动配置类Spring Boot 启动时会扫描所有 Jar 包下的这个文件聚合所有自动配置类第三方 Starter 就是靠这个文件实现 引入依赖就自动生效手写配置文件在 resources 目录下创建META-INF/my-spring.factories# 格式key自动配置入口的全限定名value配置类列表逗号分隔 com.tuling.myboot.autoconfigure.MyEnableAutoConfiguration\ com.tuling.myboot.autoconfigure.TomcatAutoConfiguration,\ com.tuling.myboot.autoconfigure.UserAutoConfiguration手写文件加载工具类对标 Spring 的SpringFactoriesLoader。public class MySpringFactoriesLoader { // 配置文件固定路径 private static final String FACTORIES_PATH META-INF/my-spring.factories; /** * 加载所有指定类型的配置类全限定名 * param factoryType 配置key的全限定名 * param classLoader 类加载器 */ public static ListString loadFactoryNames(String factoryType, ClassLoader classLoader) { ListString result new ArrayList(); try { // 1. 扫描类路径下所有的factories文件包括所有Jar包里的 EnumerationURL urls classLoader.getResources(FACTORIES_PATH); while (urls.hasMoreElements()) { URL url urls.nextElement(); // 2. 读取properties内容 Properties props new Properties(); try (InputStream is url.openStream()) { props.load(is); } // 3. 解析对应key的value拆分多个类名 String classNamesStr props.getProperty(factoryType); if (classNamesStr ! null !classNamesStr.isEmpty()) { String[] classNames classNamesStr.split(,); for (String className : classNames) { result.add(className.trim()); } } } } catch (IOException e) { e.printStackTrace(); } return result; } }3. 手写自动配置导入逻辑Spring 通过ImportImportSelector实现批量导入配置类我们复刻这套逻辑。第一步定义 Import 与 ImportSelector// 导入注解对标Import Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) public interface MyImport { Class? value(); } // 配置类选择器接口 public interface MyImportSelector { /** * 返回需要导入的配置类全限定名数组 */ String[] selectImports(Class? annotationClass); }第二步实现自动配置选择器public class MyAutoConfigurationImportSelector implements MyImportSelector { Override public String[] selectImports(Class? annotationClass) { // 1. 从factories文件加载所有自动配置类 ListString autoConfigClasses MySpringFactoriesLoader.loadFactoryNames( MyEnableAutoConfiguration.class.getName(), this.getClass().getClassLoader() ); // 2. 返回给容器后续会被注册为配置类 return autoConfigClasses.toArray(new String[0]); } }第三步定义自动配置开启注解Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) MyImport(MyAutoConfigurationImportSelector.class) // 导入选择器 public interface MyEnableAutoConfiguration { }第四步增强启动注解把自动配置能力组合到启动注解上和原生SpringBootApplication完全一致。Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented MyComponentScan MyEnableAutoConfiguration // 叠加自动配置能力 public interface MySpringBootApplication { }4. 自动配置类示例配合条件注解实现 满足条件才生效。/** * Tomcat自动配置类 * 只有类路径下存在Tomcat相关类时才会注册进容器 */ MyConfiguration MyConditionalOnClass(org.apache.catalina.startup.Tomcat) public class TomcatAutoConfiguration { MyBean public TomcatWebServer tomcatWebServer() { return new TomcatWebServer(); } }5. 常见踩坑文件格式错误类名写错、多空格、逗号用了中文格式都会导致类加载失败自动配置不生效。条件顺序错误ConditionalOnClass是类加载级别的判断要在 Bean 定义注册前执行ConditionalOnMissingBean是容器级别的判断要在所有 Bean 定义注册后执行顺序不能乱。包扫描不到自动配置类不在启动类扫描范围内所以必须靠 spring.factories 机制发现不能靠包扫描。四、Spring Boot 整合 Tomcat 底层源码分析1. 内嵌 Tomcat 的核心思想传统 SSM 项目需要外部 Tomcat打 War 包部署Spring Boot 则是把 Tomcat 封装成一个普通的 Bean放在 Spring 容器里管理容器启动时顺便启动 Tomcat直接打 Jar 包就能运行。2. 手写内嵌 Tomcat 启动第一步引入依赖dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-core/artifactId version9.0.65/version /dependency第二步封装 TomcatWebServer对标 Spring Boot 的TomcatWebServer封装启动停止逻辑。public class TomcatWebServer implements MyWebServer { private Tomcat tomcat; private final int port 8080; Override public void start() { try { // 1. 创建Tomcat核心实例 tomcat new Tomcat(); // 2. 设置服务端口 tomcat.setPort(port); // 3. 设置临时工作目录 tomcat.setBaseDir(System.getProperty(java.io.tmpdir)); // 4. 创建Web应用上下文对应一个项目 Context context tomcat.addContext(/, System.getProperty(java.io.tmpdir)); // 5. 注册Servlet真实Spring MVC会注册DispatcherServlet Tomcat.addServlet(context, demoServlet, new HttpServlet() { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType(text/html;charsetutf-8); resp.getWriter().write(h1MySpringBoot 内嵌Tomcat运行成功/h1); } }).setLoadOnStartup(1); // 6. 配置Servlet映射路径 context.addServletMappingDecoded(/, demoServlet); // 7. 启动Tomcat tomcat.start(); System.out.println(内嵌Tomcat启动完成端口 port); // 8. 阻塞等待请求防止主线程退出 tomcat.getServer().await(); } catch (LifecycleException e) { e.printStackTrace(); } } Override public void stop() { try { tomcat.stop(); } catch (LifecycleException e) { e.printStackTrace(); } } }3. 集成到启动流程在MySpringApplication.run中容器刷新后启动 Web 服务器public static MyAnnotationConfigApplicationContext run(Class? primarySource) { MyAnnotationConfigApplicationContext context new MyAnnotationConfigApplicationContext(); context.register(primarySource); context.refresh(); // 如果容器中有WebServer Bean就启动它 if (context.containsBean(tomcatWebServer)) { MyWebServer webServer (MyWebServer) context.getBean(tomcatWebServer); // 异步启动避免阻塞主线程 new Thread(webServer::start).start(); } System.out.println(MySpringBoot 启动完成); return context; }4. 源码级细节说明WebServer 抽象Spring Boot 定义了WebServer接口有 Tomcat、Jetty、Undertow 三个实现通过条件注解自动切换这就是能无缝更换 Web 容器的原因。启动时机真实源码中Tomcat 启动在ServletWebServerApplicationContext的onRefresh()方法里属于容器刷新流程的一环不是刷新完才启动。零配置原理Spring MVC 的DispatcherServlet也是靠自动配置类自动创建并注册到 Tomcat 的不用我们写 web.xml。五、自动配置类加载全流程源码梳理1. 完整执行链路图2. 关键扩展机制排除机制SpringBootApplication(exclude ...)可以手动排除不需要的自动配置类本质是在过滤阶段移除。排序机制AutoConfigureBefore、AutoConfigureAfter控制配置类加载顺序比如数据源配置必须在连接池配置之后。属性绑定ConfigurationProperties把 yml 配置绑定到 JavaBean自动配置类通过它读取用户自定义配置覆盖默认值。3. 用户配置优先的实现核心就是ConditionalOnMissingBean所有自动配置的默认 Bean 都加了这个注解用户如果自己注册了同类型的 Bean自动配置的 Bean 就不会创建保证用户自定义的优先级最高自动配置只做兜底六、面试速记总结Spring Boot 启动核心三步创建容器 → 包扫描注册 Bean 定义 → 刷新容器实例化 BeanWeb 环境额外启动内嵌 Tomcat。条件注解核心Conditional是元注解Spring Boot 扩展了类存在、Bean 缺失等常用条件判断发生在 BeanDefinition 注册阶段。自动配置本质spring.factoriesSPI 机制批量发现配置类 条件注解按需加载实现第三方 Starter 开箱即用。内嵌 Tomcat 原理把 Tomcat 封装成容器中的 Bean随 Spring 容器刷新而启动替代外部 Web 容器。手写的意义剥掉 Spring Boot 的层层封装核心还是注解解析、反射、类加载、SPI 这些 Java 基础技术理解了底层排查自动配置失效、启动失败等问题会有所帮助。