spring的启动过程面试的时候怎么说首先要明确的一点是spring的启动过程是围绕bean进行的。再然后是spring是个容器。所以首先我们要创建这个容器。也就是applicationContext这是个接口。通常我们在启动类中会有相关的实现类比如说new AnnotationConfigApplicationContext(AppConfig.class); // 或 new ClassPathXmlApplicationContext(application.xml);这里也把我们的配置文件给加载了进来。现在容器有了这时候就进入到源码中spring核心的refresh()方法。容器是用来放bean的。那就需要创建bean。怎么创建呢spring是通过BeanFactory生产bean的。那就需要创建beanFactory。factory有了后就需要加载bean了。但是要知道去哪找找bean。这时候就需要通过比如ComponentScan等方式扫描指定文件生成beanDefinition,也就是bean的公共定义像类的名称、作用域、是否懒加载、依赖的bean等。加载完了后就开始实例化bean、属性填充、初始化。bean都创建完了spring也就启动完了。这就是spring的启动流程。整体流程启动完后再补充下几点细节各个阶段预留了那些接口其实对于开发者来说我们怎么能在spring启动过程中插入我们自己的操作呢也就是spring有预留给开发者哪些接口呢这里统计以下基本上每个阶段都有接口供我们个性化操作。| 接口 | 介入时机 | 用途 | | --------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------- | | **ApplicationContextInitializer** | 容器 refresh() 之前 | 在容器刷新前修改 Environment 或容器状态 | | **BeanFactoryPostProcessor** | BeanDefinition 加载后Bean 实例化前 | 修改 BeanDefinition如 PropertySourcesPlaceholderConfigurer 处理 Value | | **BeanPostProcessor** | Bean 初始化前后 | 干预 Bean 创建AOP、Autowired、PostConstruct 都基于此 | | **InstantiationAwareBeanPostProcessor** | Bean 实例化前后 | 甚至可以自定义返回代理实例替代正常实例化 | | **Aware 接口族** | 属性填充后初始化前 | 注入容器基础设施BeanFactoryAware、ApplicationContextAware | | **InitializingBean / PostConstruct** | 属性填充后 | 自定义初始化逻辑 | | **DisposableBean / PreDestroy** | 容器关闭时 | 自定义销毁逻辑 | | **ApplicationListener** | 事件发生时 | 监听 ContextRefreshedEvent 等 | | **ApplicationRunner / CommandLineRunner** | 容器完全就绪后 | 执行启动后业务任务 |其中为我们经常最关注的是beanPostProcessor。这里能够进行AOP前面操作还有常用的PostConstruct 是在初始化的时候操作的基本上实在bean创建完成时执行的了。下面是bean实例化过程的步骤记住先进行属性填充再初始化。1. 从 singletonObjects 缓存中查找 ↓ 未找到 2. 标记该 Bean 正在创建中解决循环依赖的关键 ↓ 3. 合并 BeanDefinition处理父子 BeanDefinition ↓ 4. 检查依赖的 Bean递归 getBean() ↓ 5. 【createBean()】 ├── 5.1 实例化调用构造函数createBeanInstance() ├── 5.2 属性填充populateBean() → 执行 Autowired 注入 └── 5.3 初始化initializeBean() ├── 执行 Aware 接口回调BeanNameAware、ApplicationContextAware ├── 执行 BeanPostProcessor.postProcessBeforeInitialization() ├── 执行 PostConstruct / InitializingBean.afterPropertiesSet() └── 执行 BeanPostProcessor.postProcessAfterInitialization() ↓ 6. 放入 singletonObjects 缓存用到了哪些设计模式模板方法模式refresh()定义了固定启动步骤具体某些步骤由子类实现如onRefresh()启动 Web 服务器。就像开业流程固定但“迎宾方式”可由各餐厅自己定。工厂模式BeanFactory和ApplicationContext就是生产 Bean 的工厂单例模式Spring 管理的 Bean 默认是单例观察者模式ApplicationEventApplicationListener 启动过程有很多event类都是监听器。代理模式AOP 的核心。三级缓存里提前暴露的半成品就是用动态代理包了一层实现事务、日志等功能三级缓存三级缓存讲了很多遍了但是总感觉差点意思。问了下kimi.回答的真好主要点是如果只是解决循环依赖二级缓存也可以。但是如果要用到aop代理就必须要用到三级缓存了。因为A完成实例化后的对象在一级缓存中是A的proxy_A但是B引用的还是A的真实对象。导致AOP失效。像使用了Async\Transactional的都是需要aop的类。怎么解决aop失效的问题呢就是在三级缓存时调用getEarlyBeanReference方法aop发现A需要代理就提前生成a_proxy,把代理后的类放到B中。总之是通过判断按需处理的。Spring 三级缓存与代理类 —— 深度问答Q1三级缓存是哪三级分别存了什么表格缓存名称级别存储内容代码位置singletonObjects一级缓存成品 Bean已实例化、已注入、已初始化ConcurrentHashMapString, ObjectearlySingletonObjects二级缓存早期暴露的 Bean已实例化但未完成属性注入和初始化ConcurrentHashMapString, ObjectsingletonFactories三级缓存ObjectFactory 函数式接口一个工厂调用后返回早期 Bean 引用HashMapString, ObjectFactory?核心设计思想一级缓存存成品二级缓存存半成品三级缓存存能生成半成品的工厂。Q2三级缓存是在什么时候被使用的循环依赖场景假设 A 依赖 BB 又依赖 Aplain创建 A ├── 实例化 A调用构造函数此时 A 是个空壳属性未注入 ├── 将 A 的 ObjectFactory 放入三级缓存提前暴露 ├── 属性填充发现需要 B → 开始创建 B │ ├── 实例化 B │ ├── 属性填充发现需要 A → 从缓存找 A │ │ ├── 一级缓存没有A 还没初始化完 │ │ ├── 二级缓存没有 │ │ └── 三级缓存有调用 ObjectFactory.getObject() 拿到早期 A 引用 │ │ └── 将早期 A 放入二级缓存清空三级缓存 │ ├── 继续 B 的初始化此时 B 里的 A 是早期引用 │ └── B 创建完成放入一级缓存 ├── 回到 A继续属性填充B 已就绪 ├── A 初始化完成 └── A 放入一级缓存Q3如果只是解决循环依赖两级缓存一级 二级够吗够但前提是没有 AOP 代理。如果没有代理实例化后直接把原始对象放入二级缓存即可B 注入的就是这个原始对象等 A 初始化完成后再放入一级缓存。但有了 AOP 代理后两级缓存就出问题了。Q4代理类为什么会让两级缓存失效问题出在哪核心矛盾AOP 代理是在 Bean初始化之后才生成的BeanPostProcessor.postProcessAfterInitialization()。场景A 被 AOP 代理B 循环依赖 Aplain时间线 T1: A 实例化原始对象 A_raw T2: A 属性填充 → 需要 B → 创建 B T3: B 属性填充 → 需要 A → 从二级缓存拿 A_raw此时 A 还没初始化 T4: B 创建完成 T5: 回到 AA 初始化 → AOP 介入 → 生成代理对象 A_proxy T6: A 放入一级缓存存的是 A_proxy 问题B 中注入的是 A_raw原始对象但一级缓存中最终是 A_proxy代理对象 结果B 里的 A 不是代理对象AOP 失效这就是早期暴露原始对象 vs 后期生成代理对象的冲突。Q5三级缓存是怎么解决循环依赖 AOP 代理问题的三级缓存存的不是对象而是ObjectFactory工厂java// 实例化后放入三级缓存的代码AbstractAutowireCapableBeanFactory addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean));getEarlyBeanReference()是关键javaprotected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; // 遍历所有 SmartInstantiationAwareBeanPostProcessor // 其中就包括 AnnotationAwareAspectJAutoProxyCreatorAOP 处理器 for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { // 如果该 Bean 需要被代理这里就会提前生成代理对象 exposedObject ((SmartInstantiationAwareBeanPostProcessor) bp) .getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }流程变化plainT1: A 实例化A_raw T2: 将 () - getEarlyBeanReference(A, mbd, A_raw) 放入三级缓存 T3: A 属性填充 → 需要 B → 创建 B T4: B 属性填充 → 需要 A └── 调用三级缓存的 ObjectFactory.getObject() └── 触发 getEarlyBeanReference() └── AOP 处理器发现 A 需要代理 → 提前生成 A_proxy └── 返回 A_proxy 给 B 注入 T5: B 创建完成 T6: 回到 A继续初始化 └── 初始化完成后postProcessAfterInitialization() 再次检查 AOP └── 但 AOP 处理器发现 A 已经代理过了earlyProxyReferences 中有记录 └── 直接返回原始对象不做二次代理 T7: A 放入一级缓存结果B 注入的是 A_proxy一级缓存存的也是 A_proxy完全一致Q6代理类在三级缓存中的具体作用是什么表格作用说明延迟生成代理三级缓存存的是工厂不是对象。只有在真正发生循环依赖、需要提前暴露时才触发代理生成。如果没有循环依赖代理仍按正常流程在初始化后生成。保证单例唯一通过earlyProxyReferences集合记录哪些 Bean 已经提前代理了避免初始化后重复生成代理。解耦实例化与代理实例化阶段不直接生成代理因为属性还没注入但工厂可以在被调用时按需生成代理。打破时间差解决了循环依赖需要早期引用和AOP 代理需要后置处理之间的时间差矛盾。Q7为什么三级缓存的工厂只调用一次java// DefaultSingletonBeanRegistry.getSingleton() protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject this.singletonObjects.get(beanName); // 查一级 if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { singletonObject this.earlySingletonObjects.get(beanName); // 查二级 if (singletonObject null allowEarlyReference) { ObjectFactory? singletonFactory this.singletonFactories.get(beanName); // 查三级 if (singletonFactory ! null) { singletonObject singletonFactory.getObject(); // 调用工厂 this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级 this.singletonFactories.remove(beanName); // 清空三级 } } } return singletonObject; }关键动作一旦调用工厂拿到对象立即将结果放入二级缓存从三级缓存移除这保证了同一个 Bean 的早期引用始终只有一个后续再需要该 Bean直接从二级缓存拿不会重复生成代理Q8如果 A 和 B 互相依赖且都需要代理会生成几个代理对象答案各一个且都是正确的代理对象。plain创建 A_proxy ├── A 实例化 ├── A 放入三级缓存工厂 ├── A 属性填充 → 需要 B → 创建 B │ ├── B 实例化 │ ├── B 放入三级缓存工厂 │ ├── B 属性填充 → 需要 A │ │ └── 调用 A 的工厂 → 生成 A_proxy如果 A 需要代理 │ │ └── B 注入 A_proxy │ ├── B 初始化 → 生成 B_proxy如果 B 需要代理 │ └── B 完成放入一级缓存B_proxy ├── A 属性填充拿到 B_proxy ├── A 初始化 → AOP 发现已提前代理跳过 └── A 完成放入一级缓存A_proxy两个代理都在各自需要提前暴露时或初始化后正确生成且只生成一次。Q9Spring 为什么不允许构造器循环依赖因为三级缓存的介入时机是在实例化之后。java// 循环依赖A 的构造器需要 BB 的构造器需要 A 创建 A ├── 调用 A 的构造函数 → 需要 B │ ├── 调用 B 的构造函数 → 需要 A │ │ └── A 还没实例化完三级缓存还没有 A 的工厂 │ └── 死循环 / 报错解决方式用Lazy延迟注入Lazy private B b;注入的是代理占位符真正使用时才创建改用 Setter 注入或字段注入Q10一句话总结三级缓存与代理的关系三级缓存的本质是延迟代理策略通过ObjectFactory将代理对象的生成时机从初始化后推迟到首次被循环依赖需要时从而保证注入到其他 Bean 中的早期引用和最终成品是同一个代理对象。springboot的启动其实就是以spring为中心前面加上对服务类型的判断比如是否是web然后加载不同环境的配置application。再者就是启动快结束时利用spring预留的钩子onRefresh()创建并启动内嵌的tomcat服务器并把dispatcherServlet等关键的web bean注册到servlet容器中。当前了标识springboot最关键的还是自动配置。自动配置其本质是额外的配置类。在加载bean的阶段会到各个包的spring.factories文件中批量导入自动配置类。springcloud的启动springcloud是由一个个小的springboot组成的把这些微服务放到一个框架中大家共用一套配置。所以多了很多配置中心、注册中心的配置。启动时会先有次序的加载环境配置。bean的加载顺序我们不用管容器会根据bean之间的注入关系自动按顺序加载。为啥要用构造器注入构造器注入不解决训练依赖问题会在启动时报错的。“构造器注入是最佳实践但如果出现循环依赖你应该重新设计而不是依赖 Spring 去修补。”