ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

面试问烂的 Spring IOC 过程

面试问烂的 Spring IOC 过程 一、引言为什么 Spring IOC 会被面试官反复拷问在 Java 后端开发岗位的面试中Spring 几乎是绕不开的话题而 Spring IOCInversion of Control控制反转更是其中最基础、最核心、也最容易被面试官深挖的考点。很多候选人能够背出「控制反转就是把对象的创建权交给容器」这样的标准答案但当面试官追问「容器是怎么完成这个过程的」「bean 从解析到创建到底经历了哪些阶段」「三级缓存为什么要这么设计」时往往就支支吾吾。出现这种现象的原因并不复杂大多数人对 Spring IOC 的理解停留在「概念层面」和「使用层面」却缺少对「源码执行过程层面」的系统梳理。而面试官之所以喜欢考察这个过程正是因为 IOC 初始化链路串联起了 XML 解析、资源加载、反射、工厂模式、代理模式、缓存设计、生命周期管理、扩展点机制等大量 Java 核心知识能够非常有效地区分候选人到底是「会用 Spring」还是「理解 Spring」。本文尝试用一篇足够长的文章把「Spring IOC 的完整执行过程」从配置资源加载、BeanDefinition 解析注册、容器刷新、bean 实例化、属性填充、初始化、循环依赖处理到最终销毁掰开揉碎地讲一遍。全文以 Spring Framework 5.x 的源码为参照结合大量可运行的代码示例和高频面试题帮助你建立一条完整的、经得起追问的知识主线。阅读本文前建议你对 Spring 的基本使用有一定了解例如知道如何通过 XML 或注解定义 bean、如何从容器中获取 bean。如果完全没有接触过 Spring前面几节的概念铺垫同样可以作为入门材料但源码分析部分需要你反复阅读并动手调试。二、从一个面试题开始到底什么是 IOC2.1 控制反转的本质面试官问「什么是 IOC」本质上想考察三层递进的理解概念定义、解决的问题、与传统方式的对比。我们先从最朴素的定义出发。IOC全称 Inversion of Control中文翻译为「控制反转」。在传统的 Java 开发中一个对象如果需要依赖另一个对象通常需要由自己主动创建例如在 Service 中直接 new 一个 DAOjavapublic class UserService { private UserDao userDao new UserDaoImpl(); public User getUser(Long id) { return userDao.findById(id); } }这段代码的问题在于UserService 不仅关心自己的业务逻辑还负责创建和管理 UserDao 对象。这种「被调用者由调用者创建」的控制关系让对象之间的耦合变得非常紧密一旦 UserDaoImpl 的构造逻辑发生变化或者我们想替换为一个新的实现类就不得不修改 UserService 的源码。控制反转要做的就是把这种「对象创建和依赖关系管理的控制权」从对象自身手中反转出来交给一个外部的第三方组件去统一管理。这个第三方组件在 Spring 中就是 IOC 容器。改造后的代码是这样的java// 1. 定义接口 public interface UserDao { User findById(Long id); } // 2. 定义实现 public class UserDaoImpl implements UserDao { Override public User findById(Long id) { // 数据库查询逻辑 return new User(); } } // 3. Service 只负责声明依赖不负责创建 public class UserService { private UserDao userDao; // 通过构造器或 setter 由外部注入 public void setUserDao(UserDao userDao) { this.userDao userDao; } public User getUser(Long id) { return userDao.findById(id); } }改造之后UserService 再也不需要知道 UserDao 具体是哪个实现类也不需要关心它是如何创建的。创建对象、管理依赖关系的控制权从 UserService 手里「反转」到了 Spring 容器手里。对象本身从「主动创造者」变成了「被动接收者」。2.2 IOC 与 DI 的关系很多面试题会把 IOC 和 DIDependency Injection依赖注入放在一起问甚至有些资料把两者混为一谈。严格来说IOC 和 DI 描述的是同一件事的两个侧面IOC控制反转是一种设计思想从宏观上描述「谁控制谁」的关系发生了反转。它把对象创建与依赖管理的控制权从程序代码交到了容器手里。DI依赖注入是 IOC 的一种具体实现方式。它描述了容器如何在运行时把一个对象所依赖的其他对象「注入」进去。除了依赖注入控制反转还可以通过依赖查找Dependency Lookup等方式实现。可以这样理解IOC 是目标和原则DI 是实现这个目标的常用手段。Spring 中的「注入」主要指三种方式构造器注入、Setter 注入、字段注入注解方式。后文在属性填充阶段会详细展开。2.3 为什么要有 IOC从工程角度来看IOC 带来的收益可以归纳为以下几点降低耦合对象之间面向接口编程而不是面向具体实现替换实现类不需要修改调用方代码。集中管理对象的生命周期、作用域、依赖关系统一由容器管理项目结构更清晰。方便测试依赖可以被轻松替换为 Mock 对象单元测试更容易编写。支持扩展容器提供了丰富的扩展点BeanPostProcessor、FactoryBean 等让框架能够在容器管理的各个环节介入。支撑 AOP 与声明式事务Spring AOP 正是建立在容器对 bean 生命周期的统一管理之上。三、核心概念辨析Bean、BeanDefinition 与容器3.1 什么是 Bean在 Spring 的世界里bean 就是由 IOC 容器创建、装配和管理的对象。它本质上就是普通的 Java 对象但它与普通 new 出来的对象的区别在于bean 的创建过程、依赖注入过程、初始化过程和销毁过程都由容器代管。我们可以把它理解为「被容器托管生命周期的 Java 对象」。需要注意的是并非所有对象都适合交给容器管理。通常只有业务核心对象、服务组件、数据访问组件、基础设施组件等才需要注册为 bean而像 POJO 数据对象例如查询出来的 User 实体一般不会注册为 bean。3.2 什么是 BeanDefinition这是理解 Spring IOC 源码的关键概念。容器在真正创建 bean 之前并不会立刻实例化对象而是先把「如何创建这个 bean」的描述信息保存下来。这份描述信息就是 BeanDefinition它在源码中的定义位于 org.springframework.beans.factory.config.BeanDefinition。可以把 BeanDefinition 理解为 bean 的「设计图纸」或「配方」它描述了一个 bean 的以下信息bean 的全限定类名beanClassName作用域scope如 singleton、prototype是否懒加载lazyInit依赖的 beandependsOn构造参数constructorArgumentValues属性值propertyValues初始化方法名initMethodName和销毁方法名destroyMethodName是否主候选primary、是否自动装配候选autowireCandidate等用一句话概括配置文件或注解中的每一个 bean 定义最终都会被解析成一个 BeanDefinition 对象然后注册到容器中。后续容器的实例化工作就是根据这些 BeanDefinition 一张张地「按图施工」。3.3 什么是 IOC 容器Spring IOC 容器是负责「读取 BeanDefinition、创建 bean、装配依赖、管理生命周期」的核心组件。在源码层面容器主要由两个顶层接口来定义BeanFactoryIOC 容器的底层接口提供最基本的 bean 获取能力是 Spring 内部使用的基础容器。ApplicationContextBeanFactory 的子接口在 BeanFactory 的基础上扩展了国际化、事件发布、资源加载、环境抽象等企业级能力是面向使用者的高级容器。日常开发中直接使用的是 ApplicationContext而它内部组合了一个 BeanFactory 来承担 bean 的创建与存储。这个「内嵌的 BeanFactory」最重要的实现类就是 DefaultListableBeanFactory它是理解 Spring IOC 源码时必须牢记的核心类。四、Spring IOC 容器体系架构4.1 BeanFactory 接口体系在深入执行过程之前先把容器相关的类体系梳理清楚否则看源码时很容易迷失在层层接口之间。BeanFactory 是容器体系的根接口只定义了获取 bean、判断 bean 是否存在、获取 bean 类型等基础能力重要的子接口包括HierarchicalBeanFactory支持父子容器。ListableBeanFactory支持按类型批量获取 bean、获取所有 bean 名字。AutowireCapableBeanFactory支持自动装配和管理 bean 生命周期。ConfigurableBeanFactory提供配置能力如设置类加载器、添加 BeanPostProcessor。而最常用的 DefaultListableBeanFactory 同时实现了上述多个接口是功能最完整的 BeanFactory 实现。在后文源码中beanDefinitionMap、singletonObjects 等核心容器就位于这个类中。4.2 ApplicationContext 接口体系ApplicationContext 在 BeanFactory 的基础上派生出了更多能力其继承关系大致是javaBeanFactory - HierarchicalBeanFactory - ListableBeanFactory - ApplicationContext - ConfigurableApplicationContext - AbstractApplicationContext - ClassPathXmlApplicationContext - AnnotationConfigApplicationContext从继承关系可以看出ApplicationContext 本质上仍然是一个 BeanFactory但它额外组合了资源加载、事件发布、环境配置等模块。AbstractApplicationContext 是几乎所有 ApplicationContext 实现类的公共基类而我们在分析初始化过程时要重点看的 refresh() 方法就定义在这个抽象类中。4.3 常见容器实现类对比容器实现类配置来源常用场景ClassPathXmlApplicationContext类路径下的 XML 文件传统 XML 配置项目FileSystemXmlApplicationContext文件系统中的 XML 文件指定绝对路径的 XMLAnnotationConfigApplicationContext注解配置类基于 Java 配置与组件扫描的项目AnnotationConfigWebApplicationContext注解配置类Web 环境虽然配置来源不同但这些容器最终都会走同一个初始化入口AbstractApplicationContext.refresh()。理解了这一条主线再去读不同容器的源码就会轻松很多。五、容器启动与初始化的全景图refresh 方法5.1 从一个最经典的入口开始无论使用 XML 还是注解配置Spring IOC 容器的创建和刷新都可以归纳为下面这种形式java// XML 方式 ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml); UserService userService context.getBean(userService, UserService.class); // 注解方式 ApplicationContext context new AnnotationConfigApplicationContext(AppConfig.class); UserService userService context.getBean(UserService.class);以 ClassPathXmlApplicationContext 为例构造函数的调用链大致如下javapublic ClassPathXmlApplicationContext(String configLocation) { this(new String[] {configLocation}, true, null); } public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) { super(parent); setConfigLocations(configLocations); if (refresh) { // 核心入口刷新容器 refresh(); } }可以看到真正启动容器的是 refresh() 方法。这个方法是整个 Spring IOC 初始化过程的总纲它定义在 AbstractApplicationContext 中源码虽然不长但每一步都极其关键。5.2 refresh 方法的十二个步骤下面是 refresh() 方法的核心骨架已做精简和注释javapublic void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 刷新前的准备记录启动时间、校验环境变量、初始化早期事件监听器集合 prepareRefresh(); // 2. 获取新的 BeanFactory并加载、解析、注册所有 BeanDefinition ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory(); // 3. 对 BeanFactory 进行准备工作例如设置类加载器、SpEL 解析器、注册默认环境 bean prepareBeanFactory(beanFactory); try { // 4. 子类扩展点允许子类在 bean 工厂创建后做一些后置处理 postProcessBeanFactory(beanFactory); // 5. 执行 BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册 BeanPostProcessor registerBeanPostProcessors(beanFactory); // 7. 初始化消息源国际化相关 initMessageSource(); // 8. 初始化事件多播器 initApplicationEventMulticaster(); // 9. 子类扩展点允许特定上下文刷新时初始化特殊 bean onRefresh(); // 10. 注册事件监听器 registerListeners(); // 11. 实例化所有非懒加载的单例 bean finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新发布 ContextRefreshedEvent 事件 finishRefresh(); } catch (BeansException ex) { // 销毁已创建的 bean destroyBeans(); throw ex; } finally { // 清理缓存 resetCommonCaches(); } } }这 12 个步骤构成了容器的完整启动流程。对面试而言并不需要把每一步的源码都倒背如流但至少要能说出每一步的职责以及 bean 的定义注册发生在哪个步骤、单例 bean 的实例化发生在哪个步骤。下面我们把其中最重要的几个步骤拆开详细分析。六、第一步配置资源定位与加载6.1 从 obtainFreshBeanFactory 开始refresh 方法的第二步调用的是 obtainFreshBeanFactory()javaprotected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); return getBeanFactory(); }refreshBeanFactory() 会创建一个全新的 DefaultListableBeanFactory然后调用 loadBeanDefinitions() 去加载配置。以 XML 方式为例最终会走到 AbstractXmlApplicationContext.loadBeanDefinitions()创建一个 XmlBeanDefinitionReader由它负责真正解析 XML。6.2 Resource 资源抽象Spring 并没有直接操作 File而是抽象出了 Resource 接口用来统一表示各种来源的配置资源ClassPathResource类路径下的资源。FileSystemResource文件系统资源。UrlResource网络资源如 http/https 地址。ByteArrayResource字节数组资源。配置文件路径的解析规则同样值得注意。以 ClassPathXmlApplicationContext 为例配置路径在没有前缀时默认按类路径资源处理如果以 file: 开头则按文件系统资源处理。理解了 Resource 抽象就理解了 Spring 为何能无缝支持各种配置来源。6.3 XML 的 DOM 解析与校验XmlBeanDefinitionReader 拿到 Resource 后会借助 DocumentLoader 把 XML 文档解析为 DOM Document 对象。默认实现是 DefaultDocumentLoader它使用 JAXP 创建 DocumentBuilder并设置 EntityResolver 来校验 XML 的 DTD 或 XSD 约束。对于 Spring XML 配置来说bean 的定义规则定义在 spring-beans.xsd 等 schema 文件中解析器会根据命名空间自动匹配对应 schema因此我们在写配置文件时才能获得校验和提示能力。解析得到 Document 后XmlBeanDefinitionReader 会调用 registerBeanDefinitions(Document doc, Resource resource)把后续工作交给 DefaultBeanDefinitionDocumentReader。它遍历beans根节点处理import、alias、bean和自定义命名空间元素完成从 XML 元素到 BeanDefinition 的转换。七、第二步BeanDefinition 的解析与注册7.1 XML 配置的 bean 解析流程进入 DefaultBeanDefinitionDocumentReader.parseBeanDefinitions 后Spring 会区分默认命名空间和自定义命名空间。默认命名空间里的bean元素由 BeanDefinitionParserDelegate.parseBeanDefinitionElement 解析还支持import导入其他配置文件以及alias为 bean 起别名。以最典型的 bean 配置为例xmlbean iduserService classcom.example.UserService scopesingleton init-methodinit property nameuserDao refuserDao / /bean这里的 id、class、scope 等属性会被读取并封装到 GenericBeanDefinition 中。最有价值的细节是容器此时只是创建了描述对象并没有真正 new 出 UserService。一个bean元素对应一个 BeanDefinition而一个 BeanDefinition 又被包装为 BeanDefinitionHolder同时携带 beanName 和 alias 信息。7.2 BeanDefinition 注册到容器解析完成后XmlBeanDefinitionReader.registerBeanDefinitions 会循环调用 BeanDefinitionReaderUtils.registerBeanDefinition最终落到 DefaultListableBeanFactory.registerBeanDefinition。这个方法的逻辑非常直白javapublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) { synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); } if (!beanDefinition.isLazyInit()) { this.nonLazyInitBeanNames.add(beanName); } }从简化代码可以看出所有 bean 定义最终存放在 DefaultListableBeanFactory 的两个成员变量中beanDefinitionMap 是一个哈希表用于按名称快速查找beanDefinitionNames 是一个有序列表用于保证后续预实例化时的顺序尽可能符合配置声明顺序。别名则存储在 aliasMap 中。如果同一个 beanName 重复注册Spring 默认会根据 allowBeanDefinitionOverriding 的配置决定是否覆盖。正式开发中建议保留默认的允许覆盖策略同时尽量避免同名 bean以免运行时产生难以排查的覆盖问题。7.3 注解配置与组件扫描式注册若使用注解驱动入口会从 AnnotationConfigApplicationContext 开始。构造器中会传入一个或一组配置类随后 AnnotatedBeanDefinitionReader 解析这些配置类本身而 ClassPathBeanDefinitionScanner 则负责扫描用户配置的包路径。核心方法是 doScanjavaConfiguration ComponentScan(com.example) public class AppConfig { } ApplicationContext context new AnnotationConfigApplicationContext(AppConfig.class);扫描器会根据 basePackages 找到所有候选类判断它们是否带有 Component 注解或带有被 Component 标记的元注解例如 Service、Repository、Controller。符合条件的类同样会被转换为 BeanDefinition 并注册到 beanDefinitionMap。值得注意的是Configuration 配置类并不会在扫描阶段被完全处理它的增强逻辑要等到 ConfigurationClassPostProcessor 执行时才完成。八、prepareBeanFactory给 BeanFactory 安装默认能力完成 BeanDefinition 注册后容器的第二步已经结束回到 refresh() 的第三步prepareBeanFactory(beanFactory)。很多人忽视这一步但实际上它决定了后续所有 bean 能否顺利使用容器提供的基础能力。它的主要工作包括设置类加载器通常使用当前线程上下文类加载器。设置 SpEL 表达式解析器让 Value(${...})、#{...} 等表达式可以被解析。注册属性编辑器例如将 Resource、File 等类型进行转换。添加 ApplicationContextAwareProcessor 和 ApplicationListenerDetector。注册可解析依赖例如让 bean 可以直接注入 BeanFactory、ResourceLoader、ApplicationEventPublisher 和 ApplicationContext。注册环境相关 bean例如 environment、systemProperties、systemEnvironment。这一步让容器从一个只会读写 BeanDefinition 的工厂升级为一个具备企业级能力的基础设施。随后 try 块内的 postProcessBeanFactory(beanFactory) 是留给子类的空模板方法大部分情况下不需要特别关注。九、BeanFactoryPostProcessor在 bean 实例化前修改定义refresh() 的第五步是 invokeBeanFactoryPostProcessors(beanFactory)。这里的 BeanFactoryPostProcessor 操作对象是 BeanDefinition而不是 bean 实例因此它发生在任何普通 bean 实例化之前。常见实现包括ConfigurationClassPostProcessor处理 Configuration 类、ComponentScan、Import、Bean 等注解配置。PropertySourcesPlaceholderConfigurer解析 ${...} 占位符将外部配置注入到 bean 定义中。CustomScopeConfigurer注册自定义作用域。编写自定义 BeanFactoryPostProcessor 可以读取并修改 BeanDefinition。这对框架开发非常有价值例如动态改变 bean 的作用域、属性值或指定额外的依赖关系。但业务应用开发中更常用的是框架已经提供的实现尤其是 ConfigurationClassPostProcessor它是注解驱动 IOC 的真正发动机。十、BeanPostProcessorbean 生命周期的核心扩展点refresh() 的第六步是 registerBeanPostProcessors(beanFactory)即注册 BeanPostProcessor。要特别区分它和上一步的 BeanFactoryPostProcessor一个操作 BeanDefinition一个操作 bean 实例。Spring 内置的很多重要能力都依赖 BeanPostProcessor例如AutowiredAnnotationBeanPostProcessor处理 Autowired、Value 注入。CommonAnnotationBeanPostProcessor处理 Resource 等 JSR-250 注解。InitDestroyAnnotationBeanPostProcessor处理 PostConstruct 和 PreDestroy。注册顺序也非常重要。Spring 会先注册实现了 PriorityOrdered 接口的后处理器再注册实现了 Ordered 接口的最后才是普通后处理器。这时虽然只是注册不会立即执行但这些后处理器会在后续 bean 创建时被逐个调用。十一、finishBeanFactoryInitialization单例 bean 的预实例化跳过消息源、事件多播器和监听器等准备工作全链路最关键的一步是 refresh() 第十一步finishBeanFactoryInitialization(beanFactory)。这一步负责完成容器的初始化收尾工作并提前实例化所有非懒加载的单例 bean。其内部会做几件重要的事冻结配置把 beanDefinitionNames 转为不可修改的快照避免后续动态变更造成不确定行为。注册类型转换服务和内嵌值解析器。处理 LoadTimeWeaverAware 等特殊 bean。调用 preInstantiateSingletons 遍历所有 beanName。preInstantiateSingletons 会逐个判断 bean 是否属于 FactoryBean、是否抽象、是否懒加载。对于满足条件的 bean统一调用 getBean(beanName)。因此理解 Spring IOC 后半程本质上就是理解 getBean 如何走完创建流程。十二、getBean 与 createBean从容器拿 bean 的主干源码getBean 是使用 Spring 时最熟悉的 API但它的实现非常长核心位于 AbstractBeanFactory.doGetBean。主干逻辑可以拆成下面几步javaprotected Object doGetBean(String name, Class requiredType) { String beanName transformedBeanName(name); // 1. 优先从单例缓存中获取完整 bean 或早期引用 Object sharedInstance getSingleton(beanName); if (sharedInstance ! null) { return getObjectForBeanInstance(sharedInstance, name, beanName, null); } // 2. 如果当前缓存在创建中且是原型直接抛异常 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // 3. 如果本容器没有尝试从父容器获取 BeanFactory parentBeanFactory getParentBeanFactory(); if (parentBeanFactory ! null) { return parentBeanFactory.getBean(name, requiredType); } // 4. 标记 beanName 为正在创建状态 markBeanAsCreated(beanName); // 5. 合并 BeanDefinition并最终调用 createBean RootBeanDefinition mbd getMergedLocalBeanDefinition(beanName); return createBean(beanName, mbd, args); }这段简化的主干代码包含很多面试常问点为什么原型 bean 重复创建需要检查为什么要先查缓存getMergedLocalBeanDefinition 又是做什么的其中合并 BeanDefinition 指的是把子 bean 定义和父 bean 定义合并为一个 RootBeanDefinition。Spring 官方推荐使用 RootBeanDefinition 作为运行时统一的 bean 模型因为它包含了最终创建所需的所有信息。当缓存中没有现成 bean 时流程继续进入 AbstractAutowireCapableBeanFactory.createBean。在这个方法里Spring 先解析 bean 的真实 Class 对象然后给 InstantiationAwareBeanPostProcessor 一个在实例化前提前创建代理对象的机会。绝大多数场景下不会提前返回于是进入核心方法 doCreateBean。十三、createBeanInstance实例化策略与构造器选择doCreateBean 的第一步是 createBeanInstance。这一步解决用哪个构造器、通过什么方式创建对象的问题。主要策略包括如果配置了 instanceSupplier优先使用 Supplier 创建。如果 bean 定义来自 Bean 方法使用工厂方法创建。如果存在 Autowired 修饰的构造器使用当前的候选构造器。否则使用默认的无参构造方法完成实例化。实例化动作由 InstantiationStrategy 完成默认使用 SimpleInstantiationStrategy 通过反射调用构造器。对于需要方法注入的场景Spring 会使用 CglibSubclassingInstantiationStrategy 创建 CGLIB 子类来支持 lookup-method 和 replace-method。需要注意的是这一步完成后的对象还非常原始依赖并未注入属性也未填充。十四、populateBean属性填充与依赖注入对象创建完成后进入 populateBean 阶段。此时 bean 的实例已经存在但内部字段基本都是空值。这个阶段要做的就是把其他 bean、普通属性、集合、配置文件中的值注入进去。主要流程如下执行 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation。判断自动装配模式AUTOWIRE_NO、AUTOWIRE_BY_NAME、AUTOWIRE_BY_TYPE。解析 propertyValues完成 setter 注入。调用后处理器的 postProcessProperties例如 AutowiredAnnotationBeanPostProcessor 在这里处理 Autowired 和 Value。构造器注入、Setter 注入和字段注入各有优缺点。构造器注入适用于强依赖能够避免对象处于不完整状态也更容易发现循环依赖问题Setter 注入适用于可选依赖字段注入最简单但隐藏依赖关系且不利于单元测试。循环依赖的处理也发生在这个阶段下一节专门展开。十五、initializeBean初始化链路属性填充完成后对象还需要执行初始化。所谓初始化并不是构造成员变量而是调用用户自定义的初始化逻辑例如连接资源、启动内部线程、校验配置等。Spring 的 initializeBean 有严格的顺序javaprotected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 1. 执行 Aware 接口 invokeAwareMethods(beanName, bean); // 2. 执行 BeanPostProcessor 的前置处理 Object wrappedBean applyBeanPostProcessorsBeforeInitialization(bean, beanName); // 3. 调用 init 方法 invokeInitMethods(beanName, wrappedBean, mbd); // 4. 执行 BeanPostProcessor 的后置处理 wrappedBean applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; }第一步主要是让 bean 感知容器相关信息例如 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。第二步PostConstruct 方法由 InitDestroyAnnotationBeanPostProcessor 触发。第三步会先判断 bean 是否实现了 InitializingBean 接口如果是则调用 afterPropertiesSet()然后再调用 XML 或注解中配置的自定义 init-method。最后一步是 AOP 代理对象生成的关键位置AbstractAutoProxyCreator 在这里判断是否需要包装为代理。十六、三级缓存与循环依赖处理循环依赖是 Spring IOC 面试中最容易被追问的深水区。当 A 依赖 BB 又依赖 A 时如果容器严格按先创建 A、再创建 B、再填充 A 的 B 属性这个顺序就会陷入无限递归。Spring 通过三级缓存机制解决了单例 bean 的 setter/字段注入场景下的循环依赖。16.1 三级缓存的具体定义三级缓存都定义在 DefaultSingletonBeanRegistry 中java// 一级缓存完整可用的单例 bean private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存提前暴露的 bean 引用尚未完成属性填充和初始化 private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 三级缓存存放能生成早期引用的 ObjectFactory private final MapString, ObjectFactory? singletonFactories new HashMap(16);一级缓存保存的是已经完整创建好的单例 bean。二级缓存保存的是提前暴露的原始引用或代理对象用于解决循环依赖。三级缓存保存的是 ObjectFactory 工厂用来在需要时生成早期引用这个生成过程就可能包含 AOP 代理逻辑。16.2 为什么需要三级缓存如果只有一级和二级缓存单纯解决循环依赖是够的但无法兼顾 AOP 代理。假设 A 需要被 AOP 增强那 B 依赖 A 时拿到的必须是代理对象。提前生成代理对象的时机不能太早因为可能会破坏 AOP 的语义也不能太晚否则无法注入。三级缓存把生成早期引用这个动作延迟到了真正被其他 bean 依赖时只有发生了循环依赖才会通过 ObjectFactory 生成早期引用从而在性能和正确性之间取得平衡。面试时如果被问为什么需要三级缓存两级不够吗可以回答二级缓存只能存储原始对象无法处理 AOP 代理场景。三级缓存通过 ObjectFactory 延迟生成代理对象并且保证在多次获取时只生成一次既解决了循环依赖又保证了代理对象的唯一性。16.3 循环依赖的处理流程以 A 依赖 B、B 依赖 A 为例处理流程大致是创建 A先实例化 AcreateBeanInstance但此时还没有填充属性。提前暴露把 A 的 ObjectFactory 放入三级缓存 singletonFactories。填充 A 的属性 B发现需要 B于是递归调用 getBean(B)。创建 B先实例化 B再把 B 的 ObjectFactory 放入三级缓存。填充 B 的属性 A调用 getSingleton(A) 时在三级缓存中找到 A 的 ObjectFactory调用它得到 A 的早期引用放入二级缓存 earlySingletonObjects并从三级缓存移除。B 拿到 A 的早期引用后完成初始化进入一级缓存。A 继续填充属性 B此时 B 已经完整可用。A 执行初始化进入一级缓存从二级缓存移除。16.4 无法解决的循环依赖场景三级缓存并不是万能的。以下场景 Spring 无法解决构造器注入的循环依赖因为构造器注入发生在实例化阶段此时对象还没有被创建无法提前暴露引用。解决办法是改用 setter/字段注入或者在其中一个依赖上加 Lazy。prototype 作用域的循环依赖prototype bean 没有缓存机制每次 getBean 都会创建一个新对象Spring 直接抛出 BeanCurrentlyInCreationException。Async 代理的循环依赖如果 AOP 代理生成得比较晚可能出现早期引用和最终对象不一致的问题。解决办法是把 Async 方法拆分到独立的 bean 中。十七、bean 生命周期的完整流程图把前面所有阶段串起来一个单例 bean 的完整生命周期如下十八、高频面试题与回答要点问题 1Spring IOC 的完整流程是什么回答主线refresh() 12 步 → obtainFreshBeanFactory 加载 BeanDefinition → invokeBeanFactoryPostProcessors 处理注解配置 → registerBeanPostProcessors 注册后置处理器 → finishBeanFactoryInitialization 预实例化单例 → getBean 触发创建 → createBeanInstance → populateBean → initializeBean → 放入一级缓存。问题 2BeanDefinition 是什么BeanDefinition 是 bean 的描述信息包含类名、作用域、依赖、构造参数、属性值、初始化方法等。它是容器按图施工的设计图纸。每个bean元素或 Component 注解都会最终被解析成一个 BeanDefinition。问题 3BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别BeanFactoryPostProcessor 操作 BeanDefinition在 bean 实例化之前执行BeanPostProcessor 操作 bean 实例在每个 bean 的初始化前后执行。前者用于修改定义后者用于增强实例。问题 4三级缓存为什么要这么设计一级存完整 bean二级存早期引用三级存 ObjectFactory。三级缓存的核心目的是在解决循环依赖的同时兼顾 AOP 代理场景只有在真的发生循环依赖时才通过 ObjectFactory 延迟生成早期引用避免提前生成代理对象带来的副作用。问题 5哪些循环依赖场景 Spring 解决不了构造器注入、prototype 作用域、Async 代理的循环依赖都无法用三级缓存解决。构造器注入可用 Lazy 打破prototype 需要重新设计依赖关系。问题 6bean 的初始化方法和销毁方法有哪些初始化顺序PostConstruct → InitializingBean.afterPropertiesSet → init-method。销毁顺序PreDestroy → DisposableBean.destroy → destroy-method。问题 7为什么要区分 Aware 接口和 BeanPostProcessorAware 接口让 bean 感知自身在容器中的位置例如 BeanNameAware、BeanFactoryAware、ApplicationContextAware。BeanPostProcessor 是框架给开发者提供的通用扩展点可以在所有 bean 的初始化前后插入自定义逻辑。十九、总结回到最初的问题Spring IOC 的完整执行过程。一个完整的回答应该覆盖四层概念层IOC 是控制反转的设计思想DI 是它的实现方式容器负责对象的创建、装配和生命周期管理。注册层BeanDefinition 是 bean 的设计图纸通过 XML 解析或注解扫描生成注册到 DefaultListableBeanFactory 的 beanDefinitionMap 中。创建层refresh() 的 12 步定义容器启动主线核心是 getBean → createBeanInstance → populateBean → initializeBean期间通过三级缓存解决循环依赖。扩展层BeanFactoryPostProcessor 修改定义BeanPostProcessor 增强实例Aware 接口感知容器PostConstruct/PreDestroy 管理生命周期。
返回列表