面试攻略:如何应对 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 等具体例子。
- 准备追问:想好"如果我自己写会怎样"的答案。
- 逻辑清晰:从"为什么"到"做什么"到"怎么做"。
结语
面对层层追问,保持条理,抓住主线,就能从容应对。希望这篇攻略帮你在面试中脱颖而出!