面试攻略:如何应对 Spring 启动流程的层层追问
在面试中,"Spring 的启动流程"是一个经典问题,尤其是遇到喜欢"层层深入"的面试官时,如何回答才能既清晰又全面?本文将模拟一个真实的面试场景,以对话形式展示如何回答"Spring 的启动流程是什么?",并应对后续的追问。
面试场景:你 vs 犀利面试官
Q1:Spring 的启动流程是什么?
你 :好的,我以 AnnotationConfigApplicationContext
为例,讲一下 Spring 的启动流程。简单来说,它像建一个工厂,从零到运行,核心是 refresh()
方法。流程可以分成以下几个步骤:
- 创建容器 :通过
new AnnotationConfigApplicationContext(AppConfig.class)
初始化,加载配置类。 - 启动流程 :调用
refresh()
,这是总指挥,包含以下子步骤:- 准备阶段 :
prepareRefresh()
,初始化环境和属性。 - 建厂房 :
obtainFreshBeanFactory()
和prepareBeanFactory()
,创建并配置BeanFactory
,加载 Bean 定义。 - 改蓝图 :
invokeBeanFactoryPostProcessors()
,调整 Bean 定义。 - 招工人 :
registerBeanPostProcessors()
,注册加工员。 - 搭广播:初始化事件系统(消息源和多播器)。
- 加设备 :
onRefresh()
,子类扩展。 - 生产 :
finishBeanFactoryInitialization()
,创建所有单例 Bean。 - 开业 :
finishRefresh()
,发布容器就绪事件。
- 准备阶段 :
策略:先给一个简洁的总览,用"建工厂"的比喻让面试官抓住主线,然后暗示有细节可展开。
Q2:你提到 refresh()
是核心,能具体讲讲它为什么这么重要?
你 :当然。refresh()
是 AbstractApplicationContext
的核心方法,它的重要性在于它是整个容器启动的"总指挥"。Spring 的 BeanFactory
只是一个基础工具,而 ApplicationContext
增加了事件、国际化等功能。refresh()
把这些功能整合起来,按顺序执行,确保容器从无到有,状态一致。比如,它会先建好 BeanFactory
,再加载 Bean,再搭事件系统,最后生产产品。如果没有 refresh()
,我们得手动调用一堆方法,太复杂了。
策略:解释"为什么",突出设计意图,展示你理解框架的深层逻辑。
Q3:那 BeanFactory
是怎么创建的?为什么要"忽略接口"?
你 :BeanFactory
的创建分两步,在 refresh()
里:
obtainFreshBeanFactory()
:创建DefaultListableBeanFactory
,扫描@ComponentScan
的包,注册BeanDefinition
,相当于画生产蓝图。prepareBeanFactory()
:配置这个工厂,比如设置类加载器、添加处理器。
至于"忽略接口",比如 BeanNameAware
、BeanFactoryAware
,Spring 在这步会忽略它们的自动注入。为什么呢?因为这些接口不是通过 @Autowired
注入的,而是由特定的 BeanPostProcessor
(如 ApplicationContextAwareProcessor
)在 Bean 初始化时手动设置。这样 Spring 保持了对 Aware 机制的控制,避免开发者误用。
面试官可能追问 :那我自己写个类实现 BeanNameAware
,会怎样?
你 :如果你实现 BeanNameAware
,在 Bean 初始化时,Spring 会通过 BeanPostProcessor
调用 setBeanName()
Hawkins: setBeanName()
,把你的 Bean 名注入给你。这样可以保证你的代码更健壮,避免潜在的 Bug。
策略:用具体例子回答"为什么忽略",并准备好应对"如果自己实现"的问题,展示实战能力。
Q4:BeanPostProcessor
是什么?跟 BeanFactoryPostProcessor
有什么区别?
你 :BeanPostProcessor
是一个接口,作用是在 Bean 实例化后、初始化前后介入,加工 Bean。比如:
- AOP 的代理对象就是通过
AbstractAutoProxyCreator
这个BeanPostProcessor
生成的。 Aware
接口的值也是它设置的。
而 BeanFactoryPostProcessor
是在 Bean 实例化前,修改 BeanDefinition
,比如 ConfigurationClassPostProcessor
解析 @Configuration
。
区别:
- 时机 :
BeanFactoryPostProcessor
在定义阶段,BeanPostProcessor
在实例阶段。 - 对象:前者改蓝图,后者加工产品。
面试官可能追问 :如果有多个 BeanPostProcessor
,执行顺序怎么定?
你 :Spring 会根据 @Order
或 Ordered
接口排序,比如 AOP 的 BeanPostProcessor
得在属性填充后执行。
策略:用例子说明功能,清晰对比区别,准备好排序的细节。
Q5:onRefresh()
是干嘛的?跟 refresh()
有什么关系?
你 :onRefresh()
是 refresh()
里的一个钩子方法,给子类用的。比如 Spring Boot 在这步启动嵌入式 Web 容器,像 Tomcat。
它跟 refresh()
的关系是:refresh()
是总流程,onRefresh()
是其中一步,不是"重新刷新",而是特定场景的扩展点。
面试官可能追问 :如果我重写 onRefresh()
,会怎样?
你 :如果你重写它,可以加自定义逻辑,比如启动一个线程池。但要注意,它只在 refresh()
里被调用,不会单独触发。
策略:突出扩展性,准备好"重写"的场景。
Q6:事件系统是怎么工作的?ContextRefreshedEvent
是什么?
你:Spring 的事件系统基于观察者模式:
ApplicationEventMulticaster
:广播站,分发事件。ApplicationListener
:听众,处理事件。- 事件 :
ApplicationEvent
的子类,比如ContextRefreshedEvent
。
ContextRefreshedEvent
是 finishRefresh()
发布的,表示"容器就绪",代码是 new ContextRefreshedEvent(this)
,通知监听器做后续工作,比如日志记录。
面试官可能追问 :怎么自定义事件?
你 :定义一个类继承 ApplicationEvent
,写个 ApplicationListener
监听它,在需要时用 ApplicationContext.publishEvent()
发布。
策略:用比喻解释,准备好自定义的代码示例。
Q7:单例 Bean 怎么实例化的?三级缓存怎么用?
你 :单例 Bean 在 finishBeanFactoryInitialization()
里通过 preInstantiateSingletons()
创建,过程是:
- 实例化:构造方法创建对象。
- 依赖注入:填充属性。
- 初始化 :调用
Aware
、@PostConstruct
、初始化方法。 - AOP:生成代理。
三级缓存解决循环依赖:
- 一级:单例池,存成品。
- 二级:早期对象,存半成品。
- 三级:工厂对象,生成代理。
例子:A 依赖 B,B 未完成,A 从二级缓存拿 B 的早期引用。
面试官可能追问 :如果不用三级缓存会怎样?
你 :会抛 BeanCurrentlyInCreationException
,因为循环依赖无法解决。
策略:分步讲解,举例说明缓存,准备异常场景。
应对策略总结
- 总览先行:先给简洁框架,再展开细节。
- 比喻辅助:用"建工厂"串联逻辑。
- 举例说明:用 AOP、Aware 等具体例子。
- 准备追问:想好"如果我自己写会怎样"的答案。
- 逻辑清晰:从"为什么"到"做什么"到"怎么做"。
结语
面对层层追问,保持条理,抓住主线,就能从容应对。希望这篇攻略帮你在面试中脱颖而出!