news 2026/10/15 5:50:17

Spring Bean生命周期深度解析:从容器视角到源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Bean生命周期深度解析:从容器视角到源码实现

Spring Bean 的生命周期,这个知识点在 Java 面试里属于“必考题”,在实际开发里也是排查问题绕不开的核心区域。很多开发者能背出“实例化、属性填充、初始化、销毁”这几个阶段,但真到了项目里,碰到 Bean 没有被预期初始化、AOP 切面没生效、循环依赖报错这类问题时,还是经常一头雾水。这篇文章我想抛开教科书式的列举,直接从“容器视角”把 Bean 的一生讲透——它从哪来,中间经历了哪些关键环节,每个环节里有哪些钩子可以被我们利用,以及源码层面那个最核心的doCreateBean方法到底做了什么。这不仅能帮你理顺知识体系,更重要的是,以后遇到 Bean 相关的诡异问题,你会有清晰的排查思路。

这篇文章适合所有使用 Spring 的开发者,不管你是刚入门的新手,还是写了好几年业务代码的老手,都值得把生命周期这件事彻底搞明白。尤其是那些想在面试里把“生命周期”从“背诵题”变成“加分项”的人,这篇文章能让你讲出别人讲不出的细节。

1. 生命周期全景概览:从注册到销毁的“成长旅程”

1.1 容器里的 Bean 不是“一键生成”的

很多人把 Bean 理解成“配置好了就自动出来”,这其实是个误区。Spring 容器里的每一个 Bean,本质上都是经过一套精密的“流水线”加工出来的产物。而且这套流水线不是只在应用启动时跑一次,对于原型(Prototype)作用域的 Bean,每次获取都会完整地走一遍创建流程。

整套生命周期从宏观上看,大致可以分为四个大阶段:元数据加载与注册、实例化与属性填充、初始化、使用与销毁。但真正的细节远比这四个词要复杂。举个例子,属性填充阶段要处理依赖注入和循环依赖的问题,初始化阶段还要经过 BeanPostProcessor 的前后置处理、各种 Aware 接口的回调、以及自定义初始化方法的执行。任何一个环节出了问题,Bean 都无法按照预期投入使用。

我从容器视角把这整个过程类比成“一条流水线上的产品加工”:Spring 容器是工厂,Bean 定义(BeanDefinition)是加工图纸,BeanPostProcessor 是流水线上的质量检测员和功能增强员,而最终的 Bean 就是出厂后的成品。理解了这个类比,你在看后面每一个阶段时都会觉得顺理成章。

1.2 四个阶段的核心任务与容器角色分工

第一阶段是Bean 定义的注册与解析。在容器启动时,Spring 会读取配置类、XML 或者注解,把每个 Bean 的信息封装成BeanDefinition对象,注册进BeanDefinitionRegistry。这一步做的事情是“登记造册”,告诉容器有哪些 Bean 需要管理,它们的类是什么、作用域是什么、依赖哪些其他 Bean、初始化方法叫什么名字。这一点很多人容易忽略,它其实不是生命周期的一部分,但它是生命周期能够开始的前提。

第二阶段是实例化(Instantiation)。当容器需要某个 Bean 时,会通过BeanFactory.getBean触发创建流程。Spring 会根据 BeanDefinition 中的信息,采用构造器反射、工厂方法、或者Supplier等方式创建出 Bean 的原始实例。注意,这个阶段完成时,Bean 还是一个“裸”的对象,所有属性都是默认值。

第三阶段是属性填充(Populate)。Spring 会把 BeanDefinition 里记录的属性值、依赖的 Bean 逐个填入到实例中。这个阶段也是依赖注入真正发生的时刻。如果属性是一个引用类型,Spring 会先递归地去容器中获取这个依赖的 Bean,再注入进来。

第四阶段是初始化(Initialization)。这一步不是指 JVM 层面的对象初始化,而是 Spring 提供的各种回调机制生效的阶段。包括 Aware 接口的回调、BeanPostProcessor的前置处理、@PostConstruct方法、InitializingBean.afterPropertiesSet、自定义的init-method,以及BeanPostProcessor的后置处理。这个阶段完成之后,Bean 才真正“毕业”,可以交给业务代码使用了。

最后还有一个容易被忽略的销毁(Destruction)阶段。对于单例 Bean,在容器关闭时会触发销毁逻辑,执行@PreDestroy、DisposableBean.destroy和自定义的destroy-method。而对于原型 Bean,容器不负责销毁管理,这是个高频坑点,后面我会专门讲。

2. 生命周期关键节点拆解:初始化前后的那些扩展点

2.1 Aware 系列接口:让 Bean 感知到容器

在 Bean 生命周期的初始化阶段,有一个很重要的环节是Aware接口族回调。所谓 Aware,翻译过来是“感知”。Spring 提供了大量 xxxAware 接口,比如BeanNameAware、BeanFactoryAware、ApplicationContextAware、EnvironmentAware、ResourceLoaderAware等等。它们的作用是让 Bean 在创建过程中拿到容器内部的各种资源。

这里我解释一下为什么要设计这套机制。通常情况下,Bean 只管自己的业务逻辑,不应该去关心容器的细节。但有些场景就是需要访问容器资源,比如获取当前环境的配置信息、拿到 ApplicationContext 发布事件、或者获取容器中的其他 Bean。Spring 通过在初始化阶段回调这些 Aware 接口,把资源注入给 Bean。

顺序上需要注意:BeanNameAware和BeanFactoryAware是优先于ApplicationContextAware触发的。原因是ApplicationContextAware的注入是通过一个叫ApplicationContextAwareProcessor的内部 BeanPostProcessor 完成的,它排在比较靠后的位置。这个顺序了解就行,实际开发中用得多的还是ApplicationContextAware,比如在非 Spring 管理的类里获取容器句柄。

2.2 BeanPostProcessor:生命周期中最“万能”的钩子

如果要评选生命周期里最强大的扩展点,BeanPostProcessor当之无愧。它不是针对某一个 Bean 的,而是对容器中所有 Bean 生效的“全局拦截器”。它提供了两个方法:postProcessBeforeInitialization和postProcessAfterInitialization。

前者的调用时机在Aware回调之后、@PostConstruct和InitializingBean之前;后者的调用时机在所有初始化动作完成之后。这两个方法几乎是 Spring 内部所有高级功能的“地基”。AOP 的动态代理就是通过postProcessAfterInitialization实现的——Spring 在这里检查该 Bean 是否需要被代理,如果需要,就返回一个代理对象替换掉原始 Bean。属性值填充的@Autowired解析,靠的也是AutowiredAnnotationBeanPostProcessor这个 BeanPostProcessor 在扫描和注入。

所以你可以这样理解:我们平时写的业务代码,本质上都在和各种 BeanPostProcessor 打交道。比如@Async、@Transactional、@Cacheable这些注解,它们能够在运行时增强方法,底层都是通过 BeanPostProcessor 找到候选 Bean,然后生成代理对象来实现的。

自己实现一个 BeanPostProcessor 并不复杂,但在实际项目中必须非常谨慎。因为它是全局生效的,一个不小心,就可能导致容器里的每个 Bean 都被影响。我的建议是:如果在项目中需要自定义 BeanPostProcessor,一定要在逻辑里做好类型判断,否则性能开销和意外行为都很让人头疼。

2.3 初始化方式的选择:@PostConstruct、InitializingBean 还是 init-method

初始化阶段有三种常见的方式可以执行自定义逻辑,它们的执行顺序是固定的:@PostConstruct注解方法最先执行,然后是InitializingBean接口的afterPropertiesSet()方法,最后才是配置里的init-method。

为什么顺序是这样的?根源在于 Spring 源码里initializeBean方法的调用顺序。@PostConstruct是由CommonAnnotationBeanPostProcessor处理的,这个处理器在invokeInitMethods之前执行;InitializingBean和init-method则是在invokeInitMethods这一步里被调用的。在invokeInitMethods内部,还会先判断 Bean 是否实现了InitializingBean接口,如果实现了就先调用afterPropertiesSet(),然后再通过反射调init-method。

实际开发中,最推荐的还是使用@PostConstruct。原因很简单:它与 Spring 耦合度最低,是 JSR-250 规范里的标准注解,将来即使做框架层面的替换,改动成本也小。而且@PostConstruct支持多方法标注,InitializingBean只能有一个实现方法。init-method这种方式现在用在 XML 配置维护的老项目里会多一些,新项目里已经很少见了。

这里插一个实际场景:如果你在一个事务性代码块里往缓存里预加载数据,你用@PostConstruct做初始化,大概率会碰到事务还没准备好或者动态代理尚未生成的尴尬。这个时候你要搞清楚,@PostConstruct执行时 Bean 已经完成 AOP 代理了吗?答案是没有完全完成,因为postProcessAfterInitialization里做了 AOP 代理,而@PostConstruct是在它之前执行的。如果你在这个初始化方法里调用了自身类中被@Transactional注解的方法,事务是不生效的。这个坑非常经典,值得记下来。

2.4 销毁阶段的清理工作

与初始化对应的销毁阶段同样有一组回调,执行顺序是:@PreDestroy注解方法、DisposableBean接口的destroy()方法、以及自定义的destroy-method配置。单例 Bean 的销毁发生在容器关闭时,比如 Spring 容器调用close()方法。

实际开发中,@PreDestroy最常见的用途是释放资源,比如关闭线程池、取消定时任务、断开数据库连接、反注册监听器。用的时候有一个很关键的注意点:Spring 容器关闭时不会保证 Bean 之间的销毁顺序,所以不要在@PreDestroy引用其他可能已经被销毁的 Bean。如果确实需要有序释放资源,要把销毁逻辑独立出来,由一个专门的 Bean 统一管理和执行。

另外,Bean 的销毁逻辑不是无限次的。Spring 为了防止并发环境下的重复销毁,对同一个 Bean 的销毁只执行一次。这一点看源码里disposableBeans维护的逻辑就能理解,Spring 在注册阶段会包装DisposableBeanAdapter,跟踪销毁状态。

3. doCreateBean 源码走读:把生命周期落到代码层面

3.1 主干逻辑定位:AbstractAutowireCapableBeanFactory

如果要从源码层面真正理解生命周期,绕不开的就是AbstractAutowireCapableBeanFactory这个类。它在整个 Spring 容器体系里,是所有 Bean 创建的最终执行者。核心方法就是那个改变了无数 Java 开发者认知的createBean和它内部的三个关键子步骤。

我们平时通过applicationContext.getBean()获取一个 Bean 时,触发链路大概是这样的:AbstractApplicationContext.getBean转到BeanFactory.getBean,最终由AbstractAutowireCapableBeanFactory.createBean来干活。在createBean里,有两个前置环节值得注意:一是resolveBeforeInstantiation,在这个阶段会允许InstantiationAwareBeanPostProcessor有机会返回代理对象来替代正常创建流程;二是doCreateBean,这是正统的完整创建流程。

doCreateBean内部流程为:先看能否从缓存里拿到BeanWrapper,拿不到就createBeanInstance创建实例;接着applyMergedBeanDefinitionPostProcessors处理注解元数据;然后populateBean填充属性;最后initializeBean执行初始化。当我们读到这里时,其实生命周期的骨架已经出现了。

为了让你有一个直观的印象,用伪代码把主干流程整理出来:

@Override protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { // 1. 创建 Bean 实例(核心是构造器选择) BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); Object bean = instanceWrapper.getWrappedInstance(); // 2. 允许 MergedBeanDefinitionPostProcessor 处理(如 @Autowired、@Value 等) applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); // 3. 提前暴露 ObjectFactory 到三级缓存(循环依赖的关键) addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); // 4. 填充属性 populateBean(beanName, mbd, instanceWrapper); // 5. 初始化 Bean(Aware、BeanPostProcessor、init 方法都在这里) Object exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject; }

注意上面第三点的提前暴露,它其实发生在populateBean之前,这一步是 Spring 解决循环依赖的最关键手段。我在下一节会专门解释。

3.2 createBeanInstance:实例化阶段到底做了什么

createBeanInstance这一步说白了就是“把类变成对象”。Spring 会按照优先级来选择实例化方式:如果有Supplier就用供应商创建;如果有工厂方法就调用工厂方法;否则走构造器自动装配逻辑。

构造器选择是整个实例化阶段最复杂的部分。Spring 会去扫描类中的构造器,看哪些标注了@Autowired注解。如果找到唯一一个有参构造器且没有无参构造器,就直接用它来创建。如果没有标注任何构造器,就默认使用无参构造器。在实际报错中,最常见的“BeanInstantiationException”就是因为类里写了一个有参构造器,但没有用@Autowired标注,同时也没有提供默认无参构造器。

还有一个细节是SmartInstantiationAwareBeanPostProcessor的determineCandidateConstructors方法,Spring 内部会调用它来推断候选构造器。如果你在项目中遇到过“No default constructor found”这种问题,多半就是@Autowired不够明确导致的。

3.3 populateBean:属性填充与循环依赖的关系

populateBean是依赖注入发生的核心位置。这里会遍历PropertyValues,逐个把属性值填进去。对于复杂的依赖类型,还会触发对依赖 Bean 的懒加载获取。而在这个填充过程中,有一根隐线贯穿其中:循环依赖的解决不是靠填充属性本身,而是靠创建前提前暴露的早期引用。

Spring 解决循环依赖依赖三级缓存:第一级是singletonObjects(成品缓存),第二级是earlySingletonObjects(早期暴露的临时缓存),第三级是singletonFactories(对象工厂缓存)。流程是:A 创建时发现依赖 B,就去创建 B;B 创建时发现依赖 A,此时 A 还没完成初始化,但已经提前把ObjectFactory放进了singletonFactories。于是 B 可以通过这个工厂拿到 A 的早期引用,注入到 B 里;B 创建完成后再回填给 A。这就是为什么构造器注入的循环依赖会直接报错——因为构造器调用发生在addSingletonFactory之前,A 还没把早期引用暴露出来,B 根本拿不到 A。

这段逻辑是我见过很多开发者脑子里的“浆糊点”。很多人知道三级缓存,但不知道为什么要分成三级,也不知道为什么构造器注入循环依赖会挂。就是因为没有把createBeanInstance、addSingletonFactory、populateBean这三步的执行顺序串起来。你可以把addSingletonFactory理解成“提前开了一扇后门”,这扇后门必须在属性填充发生之前就打开,B 才有机会从后门瞄到 A 的存在。

3.4 initializeBean:Aware、BeanPostProcessor、init 方法一网打尽

initializeBean是生命周期中信息量最大的一段逻辑,可以拆成三个小步骤来看。

第一步是invokeAwareMethods,主要回调BeanNameAware、BeanFactoryAware和ClassLoaderAware。注意这里没有回调ApplicationContextAware,原因上面提过,它要通过ApplicationContextAwareProcessor在 BeanPostProcessor 里完成回调。

第二步是applyBeanPostProcessorsBeforeInitialization,也就是所有BeanPostProcessor.postProcessBeforeInitialization方法的执行,@PostConstruct的解析就发生在这一小步里。第三步才是invokeInitMethods,先执行afterPropertiesSet,再调用自定义的init-method。最后一步是applyBeanPostProcessorsAfterInitialization,这也是 AOP 代理生成的位置。代码层面的顺序记忆方式如下:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { // 第一步:Aware 接口回调(invokeAwareMethods) // 第二步:postProcessBeforeInitialization // 第三步:invokeInitMethods(afterPropertiesSet + init-method) // 第四步:postProcessAfterInitialization }

这里有一个很值得玩味的细节:postProcessAfterInitialization返回的对象可能会和传入的对象不是同一个。也就是说,Bean 在创建过程中可以被“调包”。Spring 的 AOP 代理就是这个调包的具体实现。如果你自定义了 BeanPostProcessor,请一定要记得返回处理后的对象,如果直接返回null,Spring 会视为该 Bean 没有处理器介入,可能会导致链路断裂。

3.5 源码走读的落地实践:断点调试怎么打

很多时候看源码容易“看了就忘”,我的建议是直接在你的 Spring Boot 项目里打断点,启动 Debug 模式。断点打在AbstractAutowireCapableBeanFactory.doCreateBean方法入口,然后逐步往下走,看到populateBean进循环依赖、看到initializeBean进BeanPostProcessor。把每一步涉及的对象变化记录下来,远比背下一个流程清单要牢固得多。

我自己就是这样把 Spring 的生命周期彻底吃透的,第一遍跟着走很陌生,第二遍走完基本就对整个目录结构有了肌肉记忆。调试的时候注意几个观察点:beanName是什么、当前的 Bean 是不是被代理过(看类名是否带了 CGLIB 或 JDK Proxy 标识)、三级缓存里的singletonFactories什么时候发生变化的。当你把这几条线都顺下来,Spring 的“生命周期”对你来说就不是一个抽象概念了。

4. 生命周期相关的实战问题与排查思路

4.1 初始化顺序错乱:依赖还未就绪就执行了初始化

业务中经常遇到的一种场景是:某个 Bean 在@PostConstruct里调用了另一个 Bean 的方法,偶尔正常,偶尔报空指针。出现这种问题,首先要怀疑是否 Bean 的初始化顺序不符合预期。Spring 在默认情况下并不保证 Bean 的初始化先后顺序,它遵循的是依赖优先原则。如果 A 依赖 B,则 B 必定先创建、先初始化、先填充属性——这是强保证;但如果没有依赖关系,A 和 B 谁先初始化完全取决于 BeanDefinition 的注册顺序和解析顺序。

解决办法有三种:一是通过@DependsOn显式指定初始化顺序,但这个注解会让代码侵入性变强;二是把初始化动作从@PostConstruct挪到ApplicationReadyEvent事件监听里,等整个容器完全启动后再执行,这是最稳妥的方式;三是把相关 Bean 提升为依赖关系,通过构造器或@Autowired注入,让 Spring 自然排序。

需要特别注意的是,@PostConstruct只保证“在当前 Bean 的依赖就绪后”执行,它不保证“所有 Bean 都就绪后”执行。很多新人就在这里栽了跟头。

4.2 循环依赖:构造器注入直接报错,setter 注入可以活

循环依赖这个老生常谈的问题,在生命周期语境下值得重新审视一遍。从生命周期角度理解,你会更清晰地知道哪种循环依赖能解决、哪种不能。构造器注入的循环依赖,因为创建实例之前就需要依赖参数,导致 A 还没暴露早期引用就去找 B,B 又回头找 A 时直接死锁,Spring 直接抛出BeanCurrentlyInCreationException。

Setter 注入或字段注入的循环依赖则不同:A 实例化成功后会先把早期引用放到三级缓存,再去填充 B 的依赖时,B 能从三级缓存拿到 A 的引用,从而保证链路继续跑通。所以如果你的项目是 Spring Boot 2.6 以上版本,默认还关闭了循环依赖支持(spring.main.allow-circular-references=false),即便你的代码是 setter 注入能解决的循环依赖,也会被拒绝创建。

对这种问题,不要觉得“改个配置就能开”,这只是治标。最干净的方案是重构设计,把循环依赖本身消灭掉。如果一时半会改不动,再临时打开allow-circular-references缓解。

4.3 原型 Bean 的销毁问题:容器不负责你的“后事”

在生命周期中,单例 Bean 和原型 Bean 的“待遇”差异非常明显。单例 Bean 创建完成后,Spring 会一直持有它的引用,并在容器关闭时调用销毁逻辑。而原型 Bean 每次获取都会返回一个新实例,Spring 在创建并返回该实例后,就“撒手不管”了。也就是说,原型 Bean 的@PreDestroy和destroy-method根本不会执行。

这是一个非常隐蔽的坑。如果你的原型 Bean 里面持有资源类成员,比如数据库连接或文件句柄,那么每次获取都会新生一个,并且没人帮你回收。要解决这个问题,得自己手动管理销毁,比如用ObjectProvider获取原型 Bean,在使用完毕后显式调用 Bean 里定义的清理方法;或者在原型 Bean 中实现DisposableBean,然后在获取方捕获引用并主动调用销毁方法。

这里我比较推荐的做法:把原型 Bean 需要共享的资源放到请求作用域(Request Scope)或者自定义 Scope 中去管理,而不是让原型 Bean 自身承担资源生命周期。这样虽然多写几句配置,但资源安全很多。

4.4 AOP 代理后的 Bean:你拿到的不是“原装货”

AOP 的动态代理在postProcessAfterInitialization阶段完成。这意味着,经过代理的 Bean,容器里保存的其实是一个代理对象,而不是原始的实例。当你在业务代码里通过@Autowired注入一个被 AOP 增强的 Bean 时,拿到的也是这个代理对象。这点大多数人都知道,但少有人意识到:这个代理对象的生命周期和普通 Bean 还不完全一样。

如果你在一个普通 Bean 上通过ApplicationContextAware获取自身,结果发现调用内部方法时切面没有生效,很可能是因为你拿到的ApplicationContext.getBean()是代理对象在执行,但你调用的目标方法是this.method(),走的是原始对象。这里就离不开“自调用失效”这个概念。而从生命周期角度看,是要意识到“你手里的对象,可能已经过了不止一次加工”。

排查这类问题时,优先观察 Bean 的 className 是否包含$$EnhancerBySpringCGLIB、$Proxy等标志,这能快速判断当前对象是否是被代理过的。然后考虑是否属于自调用场景、是否事务方法被同类调用等经典问题。

4.5 生命周期相关的排查工具和技巧

Spring Boot 应用启动时,可以通过打开debug日志来看 Bean 的注册和创建过程。在application.properties里设置logging.level.org.springframework.beans.factory=DEBUG、logging.level.org.springframework.context=DEBUG,能看到大量 Bean 创建细节。但日志太多,不太推荐在生产环境长开,适合本地排查。

更高效的方式是断点配合表达式观察。在doCreateBean入口处断点,通过 IDEA 的 Evaluate 表达式查看beanName、mbd.getInitMethodName()、getBeanClassName()。同时可以观察DefaultSingletonBeanRegistry的singletonsCurrentlyInCreation集合,这个集合记录了当前正在创建的 Bean 名称,是排查“循环依赖”或者“并发创建异常”的重要依据。

如果用的是 Spring Boot 3.x 和 Spring Framework 6.x,还需留意@PostConstruct和@PreDestroy目前在jakarta.annotation包下面,依赖要和javax.annotation区分清楚,否则注解不生效。这个细节在迁移老项目时尤其容易踩。

5. 生命周期知识在项目中的实际运用

生命周期不是一个只为了面试而背的概念,它在很多项目场景里都有着直接的应用价值。比如数据初始化、缓存预热、资源释放、框架集成等,背后全都是生命周期回调的巧妙运用。

做框架级功能时,通过BeanPostProcessor可以在不改动业务代码的前提下完成对指定注解的扫描与增强,这种方式在很多二方库和中间件里极其常见。有一个典型的例子:如果你的业务系统需要给某些 Bean 上面的特定字段做自定义脱敏,可以定义注解 + 实现一个BeanPostProcessor,在postProcessAfterInitialization阶段对目标 Bean 的字段进行反射处理。这样做的好处是逻辑集中,不影响调用方代码。

再比如,你可以在ApplicationContext初始化完成后,通过SmartInitializingSingleton接口回调处理所有单例 Bean 创建完毕后的统一逻辑。这个接口是很多内部组件的“最终后手”,不依赖 XML 配置,代码侵入性也更小。结合上面讲的生命周期各阶段,你可以把这些扩展点想象成一把把钥匙,分别对应不同阶段的锁——找准了位置,你的代码才有机会在正确的时机、做正确的事。

6. 我在实际开发中的几点体会

源码看多了会发现,Spring 的设计者在生命周期的每一个步骤几乎都留了口子,而且每个口子都能找到对应的真实需求。比如 Aware 接口解决的是 Bean 需要感知容器资源的需求,BeanPostProcessor 解决的是对 Spring 容器进行全局增强的需求,DisposableBean 解决的是资源回收的需求。这种“每条需求都对应一个扩展点”的设计思路,本身就值得学习。

我在调一个定时任务的项目时,就踩过@PostConstruct里初始化ScheduledExecutorService的坑。当时以为容器会在所有 Bean 加载完后才执行这段逻辑,实际上它只保证当前 Bean 处于可初始化状态。后来把线程池初始化挪到ApplicationReadyEvent里,问题再没出现过。

还有一个细节,千万不要在构造器里做太多事情,这是很多性能问题的根源。Spring 创建 Bean 时,构造器里如果包含了耗时的 IO 操作或者远程调用,会直接拖垮整个容器启动。生命周期的设计其实一直在暗示我们:属性的准备和初始化动作应该尽量后移,让 Bean 的创建过程更轻、更快、更可控。

最后分享一个小工具思路:如果你需要在一个大型系统里直观查看全部 Bean 的生命周期执行轨迹,可以在自己的BeanPostProcessor里打印一行日志,记录每个进入的 Bean 名称和当前时间戳。这样在应用启动后,你就能拿到一份按时间排序的 Bean 创建日志,排查启动慢、依赖先后错乱的问题会舒服非常多。它本身不需要复杂的技术,但从“全链路可观测”这个角度看,性价比极高。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 5:49:12

Java volatile关键字深度解析:从内存模型到并发实战

Java面试官最爱问的volatile关键字作为一个常年坐在面试桌对面的人,我可以很负责任地告诉你:volatile是 Java 并发面试里出场率最高的关键字之一,没有意外。它看起来简单——背一背“可见性、有序性、不保证原子性”就能应付过去,…

作者头像 李华
网站建设 2026/10/15 5:49:07

Spring IoC容器与依赖注入:从原理到实战踩坑全记录

做了几年Java后端,Spring和Spring Boot的代码写了不老少,但说句实话,以前那些Bean、IoC、AOP的概念在我脑子里一直是“知道大概,细问就慌”的状态。这次给自己定了个目标,老老实实把Spring的底子重新啃一遍&#xff0c…

作者头像 李华
网站建设 2026/10/15 5:49:04

Python校园兼职平台:轻量可信匹配与订单状态机实战

简介:本资源是一份完整的Python毕业设计文档,面向计算机专业本科生,聚焦学生兼职平台系统的设计与实现,解决校园兼职信息不对称、申请流程不透明、线上签约与支付缺失等实际问题。文档涵盖用户管理、兼职信息发布与筛选、在线申请…

作者头像 李华
网站建设 2026/10/15 5:48:32

JavaScript汉字拆分:解决UTF-16代理对导致的字符误切问题

简介:这是一份面向前端开发者与汉字信息处理学习者的JavaScript汉字结构分析工具包,聚焦于汉字部首与部件的程序化拆分逻辑,适用于汉字教学系统、输入法开发、搜索引擎优化等场景。资源以单个Word文档(.doc)形式提供&a…

作者头像 李华
网站建设 2026/10/15 5:48:18

Spring Boot宠物医院管理系统:从需求分析到接口设计的完整实践

前阵子一位做传统生意转行的朋友找到我,说他接了一家宠物医院的数字化改造小活,想让我帮忙看看系统该怎么搭。我去现场转了一圈,印象最深的是:前台手写预约登记、病历纸堆了两大箱、药房盘点靠感觉、收费还在用Excel对账——整个就…

作者头像 李华
网站建设 2026/10/15 5:48:17

从“0x3f 第48天 复习”说起:如何把复习变成可追踪的高效学习

我一直觉得,手机备忘录里那种只写了半行、连标点都来不及打的内容,才是我们最真实的学习状态。“0x3f 第48天 复习 10:24-12;00”——标题里的分号是用中文输入法敲出来的,说明当时手速很快,根本没空切换英文标点。就是…

作者头像 李华