news 2026/10/28 4:02:44

Spring Bean生命周期:源码级拆解与实战避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Bean生命周期:源码级拆解与实战避坑

干这行这么多年,Spring Bean生命周期是被问得最多、也最容易讲混的一道题。刚工作那会儿背过八股,后来手写迷你Spring、排查线上循环依赖问题、给同事讲解三级缓存原理,才逐渐把这些阶段真正串成一条线。这篇就把我理解的完整生命周期、源码层面的关键节点,以及面试和实战里常遇到的坑一并讲清楚。

Spring Bean生命周期不是说new一个对象那么简单,它涵盖了从配置解析、实例化、属性填充、初始化到最终销毁的全过程。理解它,你才能真正看懂Spring容器做了什么、BeanPostProcessor在哪一步介入、循环依赖为什么能解决、AOP代理又是在哪个环节生效的。无论你是刚入门的Java开发者,还是准备高级岗位面试的选手,这部分内容都值得花时间彻底吃透。

1. 生命周期全景图谱:一道面试题背后的大厦

1.1 为什么Bean生命周期是理解Spring的钥匙

很多同学学Spring第一件事就是写一个Controller,配一个Service,然后注解一加就跑起来了。但一旦遇到问题就抓瞎:为什么@Autowired能注入成功?为什么AOP切面有时候生效有时候不生效?为什么循环依赖在Spring Boot里默认能跑起来?

这些问题的答案,全都在Bean生命周期里。

我习惯把Spring容器比作一家餐厅的后厨。你只需要点菜(声明一个Bean),后面的事情——买菜(解析配置)、洗菜切菜(实例化)、配菜装盘(属性填充)、上灶烹饪(初始化)、营业结束打烊(销毁)——全部由后厨流水线完成。后厨有一个个小工(BeanPostProcessor),在上菜前后可以偷偷给菜品加料(增强逻辑、生成代理)。

搞懂这条流水线,你就掌握了Spring框架里90%的“魔法”。尤其是后厨里小工(BeanPostProcessor)干活的位置,决定了AOP、事务、异步注解这些东西能不能生效。

1.2 全景流程:从配置到容器销毁的八个阶段

先看完整路径,这八个阶段是我自己梳理的,背下来后面就好理解了。

  1. BeanDefinition解析:Spring读取XML配置、注解或Java Config,生成BeanDefinition元数据。
  2. 实例化(Instantiation):通过构造器反射创建原始对象,此时对象的所有属性都是默认值。
  3. 属性填充(Populate):根据依赖注入配置,填充基本类型属性、对象引用(@Autowired、@Resource)、集合等。
  4. Aware回调:如果Bean实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware等接口,在此阶段回调,把容器信息告诉Bean。
  5. BeanPostProcessor前置处理(postProcessBeforeInitialization):在初始化前的钩子,Spring自身的ApplicationContextAwareProcessor、AutowiredAnnotationBeanPostProcessor都在这里工作。
  6. 初始化(Initialization):依次执行@PostConstruct方法、InitializingBean接口的afterPropertiesSet方法、自定义init-method。
  7. BeanPostProcessor后置处理(postProcessAfterInitialization):初始化后的钩子,AOP创建代理对象的关键位置就在这里。
  8. 使用与销毁(Destruction):容器关闭时,依次执行@PreDestroy方法、DisposableBean接口的destroy方法、自定义destroy-method。

注意,步骤7是个分水岭。很多同学理解的“Bean”其实是代理对象,真实Bean可能在步骤7就被藏起来了。

下面给一个完整速查表,接口/注解和对应阶段一目了然:

机制执行阶段典型用途
构造器实例化创建原始对象,建议注入必需依赖
@Autowired / setter属性填充依赖注入
BeanNameAware阶段4获取BeanName
BeanFactoryAware阶段4获取BeanFactory
ApplicationContextAware阶段4获取ApplicationContext
postProcessBeforeInitialization阶段5修改Bean属性、包装Bean
@PostConstruct阶段6初始化资源、校验配置
afterPropertiesSet阶段6初始化逻辑(Spring原生接口)
init-method阶段6XML时代的初始化方法
postProcessAfterInitialization阶段7AOP代理、生成代理Bean
@PreDestroy阶段8释放资源、关闭连接
destroy阶段8销毁逻辑(DisposableBean)
destroy-method阶段8自定义销毁方法

1.3 为什么单例Bean和原型Bean生命周期不一样

Bean作用域不同,生命周期管理的深度也不同。

单例(singleton)Bean的完整生命周期由Spring容器管理:从实例化到销毁全流程都走完,容器关闭时会触发销毁回调。这也是最常见的Bean形态。

原型(prototype)Bean就要特别注意:创建后容器会交付给调用方,容器只负责“生”不负责“养”。初始化回调(@PostConstruct等)会执行,但销毁回调(@PreDestroy等)根本不会自动执行。什么时候释放资源,完全取决于使用方自己。

我在项目里吃过这个亏:把某个耗时任务Bean设为prototype,里面用到了线程池,结果容器销毁后线程池一直没关闭,导致应用多次重启后线程泄露。后来改成手动调用销毁方法才解决。这个问题在面试里也经常被问,记住一句话:prototype作用域下,Spring只管生不管死。

2. 核心阶段源码级拆解:每个节点背后发生了什么

2.1 实例化:Spring怎么把一个类变成对象

实例化是整个生命周期的起点。Spring默认通过无参构造器或指定构造器来创建对象,底层是反射调用。

正常路径下,AbstractAutowireCapableBeanFactory的createBeanInstance方法会做三件事:

  • 检查BeanDefinition是否指定了Supplier或工厂方法
  • 通过构造器解析(ConstructorResolver)匹配最合适的构造器
  • 反射实例化

这里面有个重要参数:@Autowired标注在有参构造器上时,Spring会自动选择该构造器进行依赖注入。Spring框架5.x之后,如果Bean只有一个构造器,即使不标注@Autowired也会被自动使用。我见过老项目里一个人写了三四个构造器,结果Spring不知道该选哪个,直接报错。解决办法很明确:要么保留无参构造器,要么明确标注一个主构造器。

实例化阶段有个容易被忽略的细节:实例化返回的原始对象此时还是“裸”的,属性全是默认值。如果你想在实例化后、属性填充前做点事情,可以实现InstantiationAwareBeanPostProcessor接口,在postProcessBeforeInstantiation方法中返回自定义实例。Spring自身用这个扩展点实现了AOP的提前代理(AbstractAutoProxyCreator就在这一步检查是否需要为当前Bean创建代理),这也是三级缓存能解决循环依赖的关键前置条件。

2.2 属性填充与Aware回调:Bean如何“组装”起来

实例化之后进入populateBean阶段,这是依赖注入真正发生的地方。

Spring会先处理InstantiationAwareBeanPostProcessor的postProcessProperties方法,AutowiredAnnotationBeanPostProcessor利用这个方法扫描@Autowired和@Value注解,然后逐个解析依赖并注入。所以在面试里我说过一句话:@Autowired的本质是一个BeanPostProcessor在属性填充阶段干的活,推而广之,@Resource是CommonAnnotationBeanPostProcessor干的活,两者底层套路一致,都是“后置处理器扫描注解、解析依赖、强制注入”。

依赖注入在属性填充阶段完成后,Spring进入Aware回调阶段。这里多个Aware接口的调用顺序是固定的:

  • BeanNameAware:回调setBeanName,传入Bean的id/name。
  • BeanClassLoaderAware:回调setBeanClassLoader,传入加载该Bean的ClassLoader。
  • BeanFactoryAware:回调setBeanFactory,传入BeanFactory实例,用于后续手动获取其他Bean。
  • ApplicationContextAware:这个不是由BeanFactory直接回调的,而是通过ApplicationContextAwareProcessor这个BeanPostProcessor在postProcessBeforeInitialization阶段回调setApplicationContext。

每次面试我都建议候选人把这些Aware接口的触发时机背熟,尤其是ApplicationContextAware的触发位置。它不在属性填充阶段,而在初始化前的前置处理里。这解释了为什么在setter注入时拿不到ApplicationContext,但在@PostConstruct里可以。

2.3 BeanPostProcessor:Spring最强大的扩展点

如果说Bean生命周期是一条流水线,BeanPostProcessor就是可以在流水线上自由布置的检查站。

接口定义两个方法:

public interface BeanPostProcessor { default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }

这两个方法分别作用于初始化方法(@PostConstruct、afterPropertiesSet等)的前后。注意返回值:你可以返回包装后的代理对象,也可以返回原对象,甚至可以偷天换日返回一个完全不同的对象——只要类型兼容。

我自己写过一个需求:给订单服务中所有接口方法打日志,但不想侵入业务代码。最初方案是用AOP,后来发现某个遗留Bean是XML配置的老接口,直接实现BeanPostProcessor,在postProcessAfterInitialization阶段判断Bean类型,然后用JDK动态代理包一层,写日志、记录耗时、做降级开关。这么干完全绕开了AOP对类加载顺序、切面配置的依赖,效果还特别干净。

Spring内部大量核心功能都是这样钩上来的:

  • AutowiredAnnotationBeanPostProcessor:处理@Autowired
  • CommonAnnotationBeanPostProcessor:处理@Resource、@PostConstruct、@PreDestroy
  • AbstractAutoProxyCreator:创建AOP代理,支撑@Transactional、@Async
  • ApplicationContextAwareProcessor:处理ApplicationContextAware回调

所以“如何向Spring容器中动态注入Bean”“如何给Bean动态生成代理”,本质都是自定义BeanPostProcessor。

2.4 初始化三兄弟:@PostConstruct、InitializingBean、init-method

初始化阶段同一时间点有三个执行入口,顺序是固定不变的:

  1. 先执行@PostConstruct注解方法
  2. 再执行InitializingBean接口的afterPropertiesSet()
  3. 最后执行XML配置或@Bean(initMethod = "...")指定的init-method

用一段验证代码:

@Component public class LifeCycleBean { public LifeCycleBean() { System.out.println("1. 构造方法"); } @PostConstruct public void postConstruct() { System.out.println("2. @PostConstruct"); } @Override public void afterPropertiesSet() { System.out.println("3. InitializingBean.afterPropertiesSet"); } public void customInit() { System.out.println("4. init-method"); } }

配合@Bean(initMethod = "customInit")注册,运行后输出顺序固定为1234。这个顺序在源码里由initializeBean方法保证:

protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 先执行Aware回调 invokeAwareMethods(beanName, bean); // 前置处理:@PostConstruct所在阶段 Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); // 初始化:afterPropertiesSet + init-method invokeInitMethods(beanName, wrappedBean, mbd); // 后置处理:AOP代理 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; }

看到源码就明白为什么@PostConstruct总是先执行:它是靠CommonAnnotationBeanPostProcessor在postProcessBeforeInitialization里触发的,天然排在afterPropertiesSet之前。

实践建议:项目里统一用一种初始化方式。我主导的团队规范里约定:业务代码用@PostConstruct,框架性、组件性的代码用InitializingBean。init-method只用于XML配置遗留系统,新代码不写,能在代码里显式看到生命周期钩子的地方尽量不用XML魔法。

2.5 销毁阶段:资源释放的最后一道门

容器关闭时,Spring会遍历所有单例Bean,找到SmartInitializingSingleton和DisposableBean进行处理,执行顺序同样固定:

  1. 先执行@PreDestroy标注的方法
  2. 再执行DisposableBean的destroy()方法
  3. 最后执行配置的destroy-method
@Component public class LifeCycleBean { @PreDestroy public void preDestroy() { System.out.println("1. @PreDestroy"); } @Override public void destroy() { System.out.println("2. DisposableBean.destroy"); } public void customDestroy() { System.out.println("3. destroy-method"); } }

日常开发常见的一个错:把数据库连接池、线程池放在@PreDestroy里关闭,看似合理,但在某些优雅停机场景下,Spring Cloud的注册中心摘除和Bean销毁的先后顺序会引发短时间调用失败。我们的做法是:在@PreDestroy里先切流量,再关闭连接池;或者实现SmartLifecycle接口,通过getPhase控制停机阶段的优先级。

销毁阶段还有一个容器的行为值得注意:单例Bean默认在容器关闭时统一销毁,但prototypeBean不会。有人问那prototype Bean的资源怎么释放?两个办法:一是自己维护引用列表,在容器关闭钩子里手动清理;二是用一个专门的“消费者”单例Bean管理原型Bean的销毁。没有第三种自动方案。

3. 三级缓存:生命周期里最容易被神话的原理

3.1 为什么需要三级缓存

Spring的循环依赖,指的是A依赖B、B依赖A。在属性填充阶段,A需要B,B需要一个初始化的A,但A还没初始化完——正常的生命周期走不通。

我先把结论说清楚:三级缓存解决的是单例Bean在属性填充阶段发生循环依赖的问题,它不解决构造器注入的循环依赖,也不解决prototype Bean的循环依赖。

三级缓存是三个Map,名字和用途很有讲究:

/** 一级缓存:完整的成品Bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** 二级缓存:早期暴露的原始Bean,尚未完成初始化 */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); /** 三级缓存:ObjectFactory,用于生成早期引用 */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

缓存命名的设计其实透露了生命周期阶段:

  • 实例化之后,Bean还没填充属性,Spring会把一个ObjectFactory放进三级缓存
  • 发生循环依赖时,通过三级缓存的工厂方法提前拿到一个“早期引用”,放入二级缓存
  • 初始化完成,放入一级缓存,同时清理二、三级缓存

3.2 三级缓存解决循环依赖的完整时序

假设A和B互相依赖,过程大致这样:

  1. Spring开始创建A,反射实例化A得到原始对象a。
  2. 提前把a包装成ObjectFactory放入三级缓存。
  3. 开始给a填充属性,发现依赖B。
  4. 去容器里找B,发现B没有创建,于是开始创建B。
  5. Spring实例化B,把B的ObjectFactory放入三级缓存。
  6. 给B填充属性,发现依赖A。
  7. 查找A:三级缓存里有A的ObjectFactory,调用getEarlyBeanReference得到一个“早期引用”。此时如果A需要AOP,这个早期引用可能就是代理对象,但A的初始化方法(@PostConstruct等)还没执行。
  8. B拿到早期A,顺利完成属性填充,初始化完成,B被放入一级缓存。
  9. 回到A的创建流程,A拿到已经创建好的B,继续填充属性,完成初始化。
  10. A最终完成后放入一级缓存。

整个过程里,A并不是“完整生命周期走完”后才被B引用,而是处于“半成品”状态就被分享出去了。这正是三级缓存的本质:把Bean的可用性(可被引用)和完整性(已完成初始化)解耦。

3.3 从缓存看生命周期的两个延伸结论

第一个结论:为什么构造器注入无法解决循环依赖。构造器注入要求A在实例化时就把B准备好,但B实例化时又需要A的构造器参数,此时A连“半成品”状态都不存在,三级缓存里根本找不到可以提前暴露的对象,死锁直接报错。我会在项目规范里明确:核心Bean禁止构造器循环依赖,一旦出现就要重构。

第二个结论:AOP代理在三级缓存阶段就参与进来了。第三步提到getEarlyBeanReference,这个方法会检查Bean是否需要增强(是否有Advisor匹配),需要就提前创建代理对象。结合BeanPostProcessor第7步postProcessAfterInitialization也会创建代理,你可能会问:会不会重复代理?不会,Spring有earlyProxyReferences机制,通过一个Map记录哪些Bean已经被提前代理,后置阶段就跳过。

这解释了面试高频问题:循环依赖情况下,为什么注入到B里的A已经是代理对象?因为A在三级缓存阶段通过ObjectFactory提前完成了代理创建,B拿到手里就是增强后的对象。

4. 手写一个迷你Spring:自己动手理解生命周期全过程

4.1 核心设计思路

背再多理论不如动手写一遍。我曾在技术分享时带着团队用100多行代码实现过一个只支持@Component扫描和@Autowired的迷你Spring,核心就是模拟Bean生命周期的各个阶段。这段经验让我真正理解了Spring设计者的意图。

设计方向:

  • 扫描指定包下所有类,解析带@Component注解的类,注册BeanDefinition
  • 按生命周期顺序处理:实例化、属性填充、Aware回调、初始化、后置处理
  • 维护一个极简三级缓存,演示循环依赖解决
  • 定义一个BeanPostProcessor接口,验证它在生命周期中的位置

这个迷你框架虽然简陋,但跑起来后能清晰打印出每个Bean在每个阶段的调用日志。

4.2 核心代码实现

自定义注解:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MiniComponent { } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MiniAutowired { } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MiniPostConstruct { }

生命周期核心容器:

public class MiniApplicationContext { private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(); private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(); private final List<BeanPostProcessor> postProcessors = new ArrayList<>(); public MiniApplicationContext(Class<?> configClass) { // 1. 扫描包路径,注册BeanDefinition scan(configClass.getPackageName()); // 2. 预实例化所有单例Bean(模拟Spring的preInstantiateSingletons) singletonNames.forEach(this::getBean); // 3. 注册容器关闭钩子 } public Object getBean(String beanName) { Object bean = singletonObjects.get(beanName); if (bean != null) { return bean; } // 二级缓存命中:早期引用 bean = earlySingletonObjects.get(beanName); if (bean != null) { return bean; } // 三级缓存:生成早期引用 ObjectFactory<?> factory = singletonFactories.get(beanName); if (factory != null) { bean = factory.getObject(); earlySingletonObjects.put(beanName, bean); singletonFactories.remove(beanName); return bean; } return doCreateBean(beanName); } private Object doCreateBean(String beanName) { // 阶段1: 实例化 Object bean = instantiate(beanName); // 阶段2: 提前暴露到三级缓存,解决循环依赖 singletonFactories.put(beanName, () -> { // 这里会调用BeanPostProcessor实现AOP代理逻辑 return applyPostProcessorsBeforeInit(bean, beanName); }); // 阶段3: 属性填充(模拟@Autowired) populateBean(bean, beanName); // 阶段4: 初始化(@PostConstruct) invokeInitMethods(bean, beanName); // 阶段5: 完整Bean放入一级缓存 singletonObjects.put(beanName, bean); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); return bean; } private void populateBean(Object bean, String beanName) { for (Field field : bean.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(MiniAutowired.class)) { field.setAccessible(true); String dependName = field.getName(); Object dependBean = getBean(dependName); // 触发循环依赖解析 field.set(bean, dependBean); } } } private void invokeInitMethods(Object bean, String beanName) { for (Method method : bean.getClass().getDeclaredMethods()) { if (method.isAnnotationPresent(MiniPostConstruct.class)) { method.invoke(bean); } } } }

这段代码虽然简单,但套上了BeanPostProcessor之后,就能完整模拟Spring最核心的两个能力:AOP代理介入时机、循环依赖解决。

4.3 用迷你Spring验证循环依赖

定义两个互相依赖的类:

@MiniComponent public class ServiceA { @MiniAutowired private ServiceB serviceB; @MiniPostConstruct public void init() { System.out.println("ServiceA init..."); } } @MiniComponent public class ServiceB { @MiniAutowired private ServiceA serviceA; @MiniPostConstruct public void init() { System.out.println("ServiceB init..."); } }

用上面的容器跑一下,你会发现打印顺序符合三级缓存的演进:A实例化、A放入三级缓存、B实例化、B注入A(拿到早期引用)、B初始化、B入一级缓存、A继续初始化、A入一级缓存。

手写过一次,你会彻底明白:Bean生命周期不是一个线性“背下来”的东西,而是一套为了支撑依赖注入、AOP、循环依赖等能力设计的有序流程。面试时被问到“Bean生命周期和三级缓存的关系”,你就能从源码设计意图来回答,而不是背书。

5. 高频坑点与排查实录:实战中的生命周期陷阱

5.1 @PostConstruct不生效的几种场景

常见问题一:方法不是public。Spring的CommonAnnotationBeanPostProcessor会解析标注了@PostConstruct的方法,但要求方法不能是static,且不能带参数。如果你的方法写成private void init(),有些版本不会执行或者反射调用时异常。

常见问题二:Bean不是由Spring管理的。@PostConstruct只会对容器管理的Bean生效,如果自己new了一个对象,或者通过@Bean方法手动返回new出来的对象(没有交给Spring注册),注解自然不生效。

常见问题三:类被CGLIB代理了。Spring的@Configuration类默认用CGLIB增强,内部方法调用如果绕过代理,初始化逻辑可能执行两次或者根本不按预期执行。排查手段很简单:在方法里打日志,看执行了几次。

排查这类问题,我有个习惯:先确认Bean的创建路径,再确认BeanPostProcessor是否注册。启动参数加上-Ddebug=true,Bean的注册和增强过程会输出到控制台,比自己猜快得多。

5.2 循环依赖下AOP失效的真相

循环依赖场景里,A被B提前引用时,如果A需要AOP增强,那么代理对象在三级缓存阶段就会生成。这个代理对象是依据当时已经匹配的Advisor生成的。一般情况下没问题,但有一种极端情况:某个Advisor依赖BBean的属性(Advisor本身也参与循环依赖),此时Advisor可能还没完全初始化,导致A没有被代理或者代理不完整。

我在实际项目里遇到过一次:一个事务切面依赖了一个配置Bean,配置Bean和业务Bean形成循环依赖,最终结果是一条事务注解始终不生效。定位用了整整半天,后来用@Lazy注解打破循环才恢复正常。这也是为什么Spring官方文档建议:如果可以用@Lazy避免循环依赖,就别依赖三级缓存机制。

5.3 @PreDestroy不执行:优雅停机问题

Spring Boot应用在kill -9下不会执行销毁钩子,这一点必须要清楚。正常停机(kill默认信号,或调用ApplicationContext.close())才能触发@PreDestroy。

如果你用System.exit(0)退出Spring Boot应用,默认情况下Bean销毁流程也是不走的。正确做法是调用SpringApplication.exit(applicationContext, () -> 0)。Kubernetes部署场景下,Pod被驱逐时发的是SIGTERM,Spring Boot 2.3+默认会等待优雅停机(需要配置spring.lifecycle.timeout-per-shutdown-phase),这时候Bean的@PreDestroy才会真正执行。

排查走查清单我一般看三处:

  • 进程收到的信号是SIGKILL还是SIGTERM
  • 是否配置了spring.main.register-shutdown-hook=true(默认true,但确认一下没坏处)
  • 销毁方法内部是否抛异常打断了后续销毁流程

5.4 面试应答的思路:从背诵到理解

以我面过上百人的经验,Bean生命周期这道题的优秀回答不是把八个阶段背全,而是能解释“为什么”。

我建议的应答结构:

  1. 先讲完整流程:实例化、属性填充、Aware回调、BeanPostProcessor前置处理、初始化三入口(@PostConstruct -> afterPropertiesSet -> init-method)、BeanPostProcessor后置处理、销毁三出口(@PreDestroy -> destroy -> destroy-method)。

  2. 再讲扩展点:指出BeanPostProcessor是Spring最核心的扩展口,AOP、事务、@Autowired全是在这个口子上实现的。

  3. 主动提三级缓存:说明循环依赖解决的本质是“实例化和初始化分离”,早期引用通过ObjectFactory从三级缓存暴露给依赖方,AOP代理在这个环节提前介入。

  4. 提一个自己踩过的坑:比如prototype Bean不执行销毁回调、@PostConstruct在代理场景下重复执行,这些真实经验会让面试官知道你确实在生产环境里排查过问题。

这样的回答完全基于生命周期理解,比单纯背八股有说服力得多。

最后分享一点个人体会

我见过太多人把Bean生命周期当作面试题来背,背完就忘。这很正常,因为生命周期不是孤立概念,它和依赖注入、AOP、循环依赖是一张网。真正把它融会贯通的方法就一个:动手写一个迷你容器,或者出问题时顺着源码把调用栈走一遍。我最初手写三级缓存的时候,代码又丑又乱,但写完后再看Spring源码,很多原来“记不住”的设计细节一下子就通了。

如果你打算深入这块,我建议从DefaultListableBeanFactory和AbstractAutowireCapableBeanFactory两个类入手,配合断点观察doCreateBean方法的执行过程。启动一个最简单的Spring Boot工程,在doCreateBean里打断点,逐步看Bean是怎么从原始对象变成最终成品的,整个过程的记忆会比任何八股都深刻。

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

Java学生信息管理系统实战:eclipse+MySQL+JDBC增删改查

简介&#xff1a;这套基于Eclipse和MySQL的Java学生信息管理系统&#xff0c;主要是为刚接触Java语言的开发者提供一份可以运行的练手项目&#xff0c;也适合作为课程设计或期末作业的参考。系统已经完成登录与注册模块&#xff0c;并按管理员、教师、学生三种身份分别设置操作…

作者头像 李华
网站建设 2026/10/27 12:56:04

隔离内网AI Agent工程实战:本地模型+MCP+Skills+SQLite+Vue全栈落地

1. 隔离内网下的 AI Agent 工程&#xff0c;到底难在哪 先说说我为什么会碰这个题目。去年下半年开始&#xff0c;团队里陆续有人尝试用 AI Agent 做一些内部工具的自动化&#xff0c;比如自动整理工单、自动生成周报、自动跑数据核对。一开始大家都是在能连外网的开发机上玩&a…

作者头像 李华
网站建设 2026/10/15 22:38:09

chrome-devtools-mcp:AI直连浏览器DevTools协议的MCP适配层

1. 项目概述&#xff1a;这不是一个插件&#xff0c;而是一次浏览器与AI的“神经直连”“chrome-devtools-mcp”——光看这个名字&#xff0c;很多人第一反应是“又一个Chrome扩展&#xff1f;”&#xff0c;或者下意识联想到那些在chrome://extensions/里点几下就能装上的小工…

作者头像 李华
网站建设 2026/10/17 22:56:06

云豹直播系统开源版:PHP直播源码三端架构、部署与二次开发实战

简介&#xff1a;云豹直播系统开源版是一套以PHP为核心的完整直播平台源码&#xff0c;面向希望搭建、运营直播平台或进行二次开发的个人开发者与中小企业。ZIP压缩包共121.37MB&#xff0c;包含2001个文件&#xff0c;其中JavaScript文件868个、Java文件278个、XML文件237个、…

作者头像 李华
网站建设 2026/10/27 6:12:49

全能截图工具实测:长截图、OCR、录屏一体的效率工作流

上周整理一份项目复盘报告时&#xff0c;我发现自己正陷入一个极其荒诞的工作流&#xff1a;需要滚动长截图时打开A软件&#xff0c;需要贴图对照时切到B工具&#xff0c;OCR识别合同关键字段要单独开C程序&#xff0c;最后补录一段操作演示又得掏出D应用。一台电脑上光是"…

作者头像 李华
网站建设 2026/10/14 7:46:11

AI芯片软硬件协同设计:脉动阵列原理与Transformer硬件映射实战

AI 芯片这个话题&#xff0c;聊架构、聊算力、聊制程的文章已经够多了&#xff0c;但真正落到"软硬件怎么配合设计"这个层面&#xff0c;能讲清楚的人并不多。我做了几年加速器相关的软硬件协同工作&#xff0c;从 RTL 仿真到编译器算子映射都趟过一遍&#xff0c;深…

作者头像 李华