news 2026/10/23 1:58:35

Spring Bean创建过程全解析:从BeanDefinition到三级缓存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Bean创建过程全解析:从BeanDefinition到三级缓存

Spring Bean的创建过程,在Java面试里属于“必考但十个人有八个说不透”的话题。我带团队面试时习惯问一个问题:你天天写@Component、@Autowired,Spring到底是怎么把一个对象造出来的?大多数人能答到“帮我们new了一个对象”这层就停了,能继续往下说BeanDefinition、实例化策略、循环依赖三级缓存的少之又少。这篇就把这条链路从头到尾拆开讲透,覆盖四种实例化方式、完整的生命周期回调、三级缓存的设计原因,以及BeanPostProcessor如何成为AOP和各种自定义注解的发动机。适合正在准备面试的初中级开发,也适合那些Spring项目跑了几年、但遇到依赖注入报错只能靠重启解决的同事。

1. 为什么Spring要把“new一个对象”这件事搞得这么复杂

Spring整天强调控制反转,字面上就是把对象的创建权从程序员手里转交给容器。很多人把这当成一句口号背下来,却没真正理解它到底解决了什么问题。回到最朴素的世界,没有Spring的时候,一个订单服务要调库存服务,库存服务要调数据库,你的代码大概长这样:

public class OrderService { private StockService stockService = new StockService(); private UserService userService = new UserService(); }

如果UserService里又依赖了一堆别的Service,这行代码会变得非常恐怖:每一次new都会把整棵依赖树手工拉起来。更麻烦的是,如果哪天想把UserService替换成带缓存的代理实现,你得把new的这行代码全部改一遍。这就是对象之间的强耦合,改一处牵一发动全身。

Spring把创建权收走以后,你要做的只是声明“我有一个依赖”,容器负责在合适的时机把它造好、塞进来。这个改变让业务代码从对象组装中解放出来,但也带来一个巨大的代价:框架必须在对象生命周期的每个关键节点都能介入。它得知道什么时候构造、什么时候填充属性、什么时候执行初始化回调、什么时候销毁,这就是我们常说的Bean生命周期,它本质上是一套框架和对象之间的“约定”。

所以Spring Bean不是普通的Java对象。一个被Spring管理的Bean,指的是被容器实例化、组装、初始化并纳入生命周期管理、最后还能被容器销毁回收的对象。普通的new对象一旦创建出来,谁创建谁负责释放,完全不受管。而Bean从出生到销毁,每个阶段都有明确定义的回调点,这是理解整个Spring容器设计的钥匙。

明白了这一层,再去看网上铺天盖地的“三级缓存”“实例化顺序”,你会有完全不一样的感觉,不再是在背知识点,而是顺着框架设计者的思路在走。

2. BeanDefinition:Spring描述Bean的“设计图纸”

BeanDefinition在Spring的体系里是个非常关键但常被忽视的概念。很多人会问:Spring怎么知道一个类的构造参数是什么?怎么知道它是单例还是原型?怎么知道初始化方法叫什么名字?答案全都记在BeanDefinition里,它是Spring描述Bean的元数据,可以理解成对象的“设计图纸”。

2.1 一张图纸上到底写了什么

一个完整的BeanDefinition至少包含这些关键信息:

属性作用说明
beanClass真正要创建的类,也就是Class对象
scope作用域,singleton单例、prototype原型
lazyInit是否懒加载,单例对象是否延迟到首次getBean再创建
initMethodName初始化回调方法名,比如init()
destroyMethodName销毁回调方法名,比如close()
propertyValues属性值集合,XML时代的 标签就存在这
constructorArgumentValues构造器参数值
autowireMode自动装配模式,按类型还是按名称
dependsOn显式依赖列表,创建前必须先创建这些Bean
primary是否优先候选,多候选注入时使用

用代码来注册一个BeanDefinition非常简单,Spring提供了BeanDefinitionBuilder:

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(OrderService.class); builder.setScope("singleton"); builder.addPropertyValue("timeout", 3000); builder.setInitMethodName("init"); builder.setLazyInit(false); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition("orderService", builder.getBeanDefinition());

这段代码跑起来,你不需要任何Spring Boot环境,一个DefaultListableBeanFactory就是一个完整的最小容器,之后调用factory.getBean("orderService")就能拿到对象。理解这一点,是手写Spring的起点。

2.2 三种注册路径:扫描、配置类与编程式

大部分项目中的BeanDefinition,不是我们手动注册的,而是由Spring的扫描器和解析器自动生成的。

  • 注解扫描路径:ApplicationContext启动后,ClassPathBeanDefinitionScanner会扫描指定包下的@Component、@Service、@Repository等注解,把每个类包装成ScannedGenericBeanDefinition,然后注册进BeanDefinitionRegistry。这就是@Service能生效的最底层原理,不是Spring认识Service这个注解,而是扫描器把所有带有这类注解的类都登记为BeanDefinition。

  • 配置类@Bean路径:@Configuration类里的@Bean方法会被ConfigurationClassPostProcessor解析,每个@Bean方法被转换成ConfigurationClassBeanDefinition。这里有个细节:@Bean方法本质上是一个“工厂方法”,Spring不只是new一个类,而是调用那个方法来获得对象,所以返回值必须是实际要发布的类型,方法名默认就是beanName。

  • 编程式路径:有些场景,比如在单元测试里手工组装一个容器,或者要动态注册一个运行时才知道的类,可以直接构造BeanDefinition手动注册,上面那段代码就是演示。

2.3 手写Spring时最容易忽略的一个环节

如果你尝试自己搞一个迷你版Spring,最容易踩的坑就是:一上来就想“创建对象”,完全绕过了BeanDefinition。但实际上,Spring容器里的一切都从BeanDefinition开始,实例化只是拿这张“图纸”去施工。没有BeanDefinition的类,在Spring眼里根本不存在。这也解释了为什么一个没有被扫描到、也没在配置类里声明过的普通类,即使你到处写@Autowired,Spring也只会报NoSuchBeanDefinitionException,因为它压根没有图纸。

理解了BeanDefinition,你才能回答面试里那个经典问题:Spring在创建对象之前,做了哪些准备工作?答案不是一句“扫描注解”,而是“把每一个被管理的类转换成BeanDefinition,注册进BeanDefinitionRegistry,在实例化前完成所有优先级排序和依赖预处理”。

3. getBean启动的完整生命周期:从调用入口到销毁回收

先明确一个问题:单例Bean是什么时候创建的?很多人以为是getBean才创建,其实大部分单例在容器启动阶段就被创建了。ApplicationContext在refresh过程中会执行finishBeanFactoryInitialization,把所有非懒加载的单例Bean预实例化一遍。也就是说,你启动一个Spring Boot应用,看到控制台里所有Bean都在启动时被创建,这就是在批量调用getBean。但无论哪种入口,真正创建逻辑都收敛在getBean里。

3.1 getBean的前置处理:缓存、父容器与依赖排序

创建一个Bean之前,Spring要先做四件事:

  1. 解析beanName:传入的字符串可能带“&”前缀,这代表要获取FactoryBean本身而不是它生产的对象,需要先把前缀剔除,拿到真正的beanName。
  2. 查一级缓存:调用getSingleton(beanName)查找singletonObjects,这是“已完成全部初始化的单例”的存放地。如果命中了,直接返回成品,创建流程结束。
  3. 查父容器:当前容器没找到,就去父容器getBean。
  4. 处理dependsOn:如果BeanDefinition里显示当前Bean依赖了某些Bean,必须先递归创建这些前置依赖。这就是为什么两个Bean有先后依赖时,Spring能保证顺序。

这里有个容易忽略的点:查缓存这一步不只是查一级缓存,而是执行一个复杂的查找链,这涉及循环依赖的处理,后面单独展开。

3.2 doCreateBean的“三段式”:实例化、属性填充、初始化

真正创建的逻辑都在doCreateBean里,可以分成三个阶段。

第一阶段实例化,对应代码createBeanInstance。Spring根据BeanDefinition里记录的构造参数和自动装配模式,选择合适的构造器来创建对象。这里调用的还是普通的反射newInstance,所以“实例化”就是字面意思:对象被new出来了,但此刻它内部全是空值,依赖尚未注入,初始化方法尚未执行。

第二阶段属性填充,对应populateBean。Spring遍历BeanDefinition里的propertyValues,通过反射调用setter为属性赋值。同时会调用实现了InstantiationAwareBeanPostProcessor的后处理器来处理@Autowired、@Resource这些注入注解。这一阶段完成后,对象内部的依赖引用才真正可用。

第三阶段初始化,对应initializeBean。这个阶段是回调的密集区,顺序非常重要:

顺序回调类型典型代表
1Aware系列回调BeanNameAware、BeanClassLoaderAware、BeanFactoryAware、ApplicationContextAware
2BeanPostProcessor.postProcessBeforeInitialization各框架的启动前处理
3初始化方法@PostConstruct、InitializingBean.afterPropertiesSet、init-method(按顺序执行)
4BeanPostProcessor.postProcessAfterInitializationAOP代理对象生成的时机

注意一个细节:ApplicationContextAware的注入不是由Spring内核直接处理的,而是通过ApplicationContextAwareProcessor这个BeanPostProcessor完成的,这从侧面展示了后处理器的扩展威力。

3.3 单例注册与销毁回收

初始化完成后,若是单例Bean,Spring会调用addSingleton将其放入一级缓存singletonObjects,从此后续getBean都能直接命中。

销毁阶段发生在容器关闭时。单例Bean在创建完成后,会注册一个DisposableBeanAdapter到disposableBeans列表。容器执行close时,按逆序遍历List,逐个调用销毁逻辑。销毁方法的触发顺序是:@PreDestroy注解方法、DisposableBean.destroy接口方法、destroy-method指定的方法。这一套倒序的清理机制,设计上有点像try-with-resources,把资源释放的责任从业务代码里剥离开了。

如果Bean的scope是prototype,那就简单得多:每次getBean都走一遍完整创建流程,返回后容器不再追踪,也不负责销毁。这也是原型Bean经常导致内存泄漏的原因之一,谁拿走了谁负责清理。

4. 实例化方式的底层差异:构造器、工厂方法与Supplier

不少人对“实例化”的理解就只有构造器,但Spring实际上支持好几种实例化方式。面试里问到Bean实例化方式这个词时,能把这几种路径说清楚,是很大的加分项。

4.1 五种实例化路径及适用场景

实例化方式底层机制常见用途
构造器实例化反射Constructor.newInstance普通业务类,最常用
静态工厂方法Method.invoke调用static方法第三方的构造逻辑在静态方法里
实例工厂方法先创建工厂Bean,再调其方法老式XML配置遗留场景
Supplier实例化直接执行lambda代码块编程式注册、动态构造逻辑
FactoryBean调用getObject()方法框架集成,如MyBatis的Mapper扫描

构造器实例化是默认路径,Spring会通过ConstructorResolver来推断该用哪个构造器:如果只有一个构造器,直接用;有多个时,优先找被@Autowired标注的构造器,Spring 4.3之后如果一个类只有单个构造器,可以省略@Autowired。这里也解释了为什么你写了一个带参构造器以后,Spring可能会报找不到无参构造器,因为它优先尝试无参构造,再用BeanDefinition里的构造参数值去匹配带参构造,匹配失败就会抛异常。

静态工厂方法和实例工厂方法在XML时代用得比较多,现在基本被@Bean方法替代了。你要知道的是,@Bean方法本质上就是一种实例工厂方法的变体:Configuration类本身被实例化后,Spring调用其方法获取对象。

Supplier实例化是编程式注册时的优雅手段,比如运行时需要根据配置动态决定构造参数:

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConnectionPool.class); builder.setInstanceSupplier(() -> new ConnectionPool(config.getMaxSize(), config.getTimeout())); factory.registerBeanDefinition("connectionPool", builder.getBeanDefinition());

FactoryBean则要单独说,它的getObject返回值是“工厂生产的对象”。MyBatis的MapperScannerRegistrar就是典型的FactoryBean玩法:每个Mapper接口注册一个MapperFactoryBean,getObject里生成动态代理对象。所以当你在Spring里注入一个Mapper接口时,拿到的其实是一个代理,而Spring只认识FactoryBean本身。

4.2 为什么推荐构造器注入而不是字段注入

实例化方式直接影响了依赖注入的可靠性。字段注入的代码写起来很爽,但存在几个问题:依赖不透明,类的依赖全藏在私有字段里;不便测试,必须靠反射或者完整容器才能构造;更重要的是,它容易掩盖循环依赖。构造器注入则会强制你在创建对象时就把依赖交代清楚,对象要么构造完整,要么干脆创建失败,不存在“半成品”状态。

当然构造器注入也有代价:当两个Bean互相通过构造器引用时,循环依赖无法解决,因为此时根本还没有东西可以提前暴露。这也是市面上一部分人仍然坚持字段注入的原因,但作为默认规范,团队项目用构造器注入会更稳。

4.3 代理对象如何绕过构造器

有个反直觉的场景:Spring AOP最终返回的是代理对象,那代理对象本身是如何实例化的?如果目标类有构造器,JDK动态代理通过Proxy.newProxyInstance生成,它不调用目标类的构造器,而是创建一个同接口的代理类实例;CGLIB则通过生成目标类的子类,实际还是绕过了业务构造器。这也是为什么字段注入在代理场景下经常踩坑:代理对象无法正确初始化真实对象的私有字段依赖。看AOP相关的疑难杂症时,优先怀疑这一层,往往比怀疑配置语法更有效。

5. 三级缓存:循环依赖的解决方案与边界

三级缓存是Spring Bean创建过程里最被追捧、也最容易讲混的概念。要理解它,先理解它解决了什么问题。

5.1 问题复现与解决思路

循环依赖长这样:A持有B的引用,B也持有A的引用。如果都通过字段或setter注入,单纯递归式创建必死循环:创建A时要填B,创建B时要填A,A还在创建中,没有对象可以注入给B。老Java程序员都经历过EJB时代,循环依赖处理不好就是StackOverflow。

Spring的思路很直接——先创建A的早期引用,把A的一个“半成品”暴露出来,这样B创建时能拿到A的引用完成注入,B完成后,再回头把B注入给A,两个Bean就都完整了。问题在于,这个“早期引用”放在哪里,就催生了三级缓存。

5.2 一级、二级、三级缓存各自的职责

缓存名称数据结构存放内容
singletonObjectsConcurrentHashMap一级缓存,完整的单例成品
earlySingletonObjectsConcurrentHashMap二级缓存,提前暴露的早期引用
singletonFactoriesHashMap三级缓存,ObjectFactory工厂,能生成早期引用

创建过程中,Bean在实例化完成但属性尚未填充时,Spring就把一个ObjectFactory放进三级缓存。这个工厂的核心能力是:在需要时调用getEarlyBeanReference生成早期引用。getBean缓存查找的完整顺序是这样的:

  1. 先查singletonObjects,命中直接返回完整Bean。
  2. 查earlySingletonObjects,命中说明有Bean正处在循环依赖中,返回早期引用。
  3. 查singletonFactories,拿到ObjectFactory并调用getObject,把产物放入二级缓存,同时移除三级缓存里的工厂。后续再有人要这个Bean,直接从二级缓存拿。

如果你只想解决“B拿到A的引用”这个问题,其实二级缓存就够了:A实例化后直接把A放进去,B拿的就是原始对象。但Spring偏偏多加了一层三级缓存,原因只有一个——AOP。

AOP代理是在postProcessAfterInitialization阶段生成的。如果只有二级缓存,A实例化后放进二级缓存的是一个原始对象,后续A被AOP代理替换后,B手里拿到的还是原始对象,代理完全失效。除非改成:A实例化后立刻判断是否需要代理,需要就直接生成代理放二级缓存。这在没有循环依赖时会导致不必要的代理提前创建,白白损耗性能。三级缓存则把“是否生成代理”的决定延迟到真正有人依赖A的那一刻。没人循环依赖,就等到初始化后正常生成代理;有人循环依赖,就通过SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference在ObjectFactory里生成早期代理引用。

所以三级缓存的核心价值就一句话:在保持“没有循环依赖时不提前创建代理”的前提下,让循环依赖场景下注入到其他Bean的仍然是增强后的代理对象。

5.3 三级缓存解不了的循环依赖

三级缓存不是万能药,至少有三种场景它无能为力:

  • 构造器注入的循环依赖:A的构造器需要B,B的构造器需要A。实例化都没完成,没有“半成品”可暴露,直接报错。
  • 非单例Bean的循环依赖:prototype每次都是全新的,根本没有缓存的余地,一进入循环就死锁。
  • @Async标注的Bean循环依赖:异步代理和普通AOP代理不是同一个机制,即使通过早期引用暴露了,后续注入时拿到的方法增强可能不完整,表现成线程池或代理失效一类诡异问题。

Spring Boot 2.6开始默认禁止循环依赖,启动时遇到循环直接抛异常,宁可让你改设计也不要依赖这个机制。以前的版本默默允许,导致大量项目把循环依赖当常规操作,后来每次升级都崩一批。如果项目里出现循环依赖,正确姿势是重构依赖关系,拆一个中间对象出来,而不是打开allow-circular-references配置一了百了。

6. BeanPostProcessor:Spring所有“黑科技”的插件机制

前面几章反复提到了BeanPostProcessor,但它值得单独拿出来讲,因为理解了它,你就理解了Spring一半的扩展能力。

6.1 接口设计与执行时机

最核心的接口就两个方法:

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

真正意义上的服务对象是“别人”:Spring在初始化阶段自动调用所有已注册的BeanPostProcessor,传给它们当前正在创建的Bean,允许它们修改、包装、替换。返回值会被当作最终的Bean。

除了这个基础接口,还有InstantiationAwareBeanPostProcessor,它把介入时机扩展到实例化前后和属性填充阶段,分别对应postProcessBeforeInstantiation、postProcessAfterInstantiation、postProcessPropertyValues。还有SmartInstantiationAwareBeanPostProcessor,额外提供了determineCandidateConstructors(决定用哪个构造器)和getEarlyBeanReference(支撑三级缓存早期引用)。AOP的AbstractAutoProxyCreator就是SmartInstantiationAwareBeanPostProcessor的实现。

常见后处理器和能力的对应关系,整理成表:

后处理器能力
AutowiredAnnotationBeanPostProcessor@Autowired、@Value注入
CommonAnnotationBeanPostProcessor@Resource、@PostConstruct、@PreDestroy
ApplicationContextAwareProcessorApplicationContextAware回调注入
AnnotationAwareAspectJAutoProxyCreator基于注解的AOP代理
ConfigurationClassPostProcessor严格来说是BeanFactoryPostProcessor,负责解析@Configuration

6.2 一个实战案例:用自定义后处理器做方法耗时日志

很多人在项目里做“方法日志切面”,第一反应是引入Spring AOP的@Aspect。但如果你只想对某个自定义注解做非常聚焦的代理增强,完全可以自己写一个超轻量的后处理器,还能顺便加深对原理的理解。

假设定义了一个注解@MethodLog,目标是打印被标注方法的耗时。核心代码如下:

public class MethodLogBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> targetClass = bean.getClass(); if (targetClass.isAnnotationPresent(MethodLog.class) || Arrays.stream(targetClass.getMethods()).anyMatch(m -> m.isAnnotationPresent(MethodLog.class))) { return Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), (proxy, method, args) -> { long start = System.currentTimeMillis(); Object result = method.invoke(bean, args); System.out.println(method.getName() + " cost " + (System.currentTimeMillis() - start) + "ms"); return result; }); } return bean; } }

注册进容器后,凡是标注了@MethodLog且实现了接口的Bean,postProcessAfterInitialization阶段就会被替换成JDK动态代理。执行时打印耗时,原始对象通过method.invoke(bean, args)调用。整个过程你完全避开了复杂的AOP表达式,也直观理解了“代理在Bean生命周期的最后阶段替换了原始Bean”这一事实。

实际生产环境建议还是用Spring AOP或AspectJ,自己写后处理器适合小场景和学习验证,但要小心代理类型转换问题、接口缺失问题,而且没实现接口的类需要换CGLIB策略。

6.3 扩展自定义功能的标准姿势

明白了这个机制,再去看一堆框架的源码会豁然开朗:Spring Security的方法级权限校验,是后处理器把对象包了一层安全代理;Spring的@Async是AsyncAnnotationBeanPostProcessor把对象包了异步代理;OpenFeign的FeignClient注解,是注册时通过FactoryBean生成了HTTP动态代理。所有“加一个注解就多一个能力”的黑科技,底层几乎都是BeanPostProcessor在对象初始化前后动了手脚。所以如果你要给团队框架做扩展,比如自动给某些Bean做统一的前后处理,正确做法不是去改Spring源码,而是写一个BeanPostProcessor并注册进去。

7. 高频追问与实践坑位:把原理变成肌肉记忆

原理讲完了,接下来聊聊面试和实战里最常见的那些变化题。

7.1 面试官最爱追问的五个点

第一问:Bean实例化阶段到底做了什么?回答重点落在ConstructorResolver选择构造器、反射newInstance上,同时要说清此刻对象还是空壳。

第二问:依赖注入发生在哪个阶段?属性填充阶段,@Autowired的注入由后处理器完成,不是反射直接赋值。

第三问:AOP代理在什么时候生成的?postProcessAfterInitialization阶段,后处理器返回代理对象,替代原始Bean被放入容器。

第四问:@PostConstruct、InitializingBean、init-method的执行顺序?@PostConstruct先执行,接着afterPropertiesSet,最后init-method。销毁顺序反过来,@PreDestroy先,DisposableBean.destroy其次,destroy-method最后。

第五问:为什么解决循环依赖需要三级缓存而不是两级?核心是延迟AOP代理的创建时机,允许对象在实例化后先暴露为早期引用,等真正有循环依赖时再决定是否生成代理。

7.2 实践中容易翻车的几个场景

单例注入原型Bean,这是经典坑。容器启动时创建一个单例Bean,属性填充阶段把原型Bean注入进去,之后再getBean原型Bean,单例里持有的永远是当初那一个,不会每次新建。正确做法是注入ObjectFactory或者使用@Lookup。

@PostConstruct在代理对象上执行多次,这个非常隐蔽。如果某个类被AOP代理,而代理机制的某个环节又触发了一次对象创建,原始对象和代理对象各自执行一次@PostConstruct,你会在日志里看到初始化逻辑跑了两遍。排查思路是检查是否为该Bean配置了多个后处理器,以及是否有工厂方法生成对象后再次进入初始化。

高版本Spring Boot循环依赖直接抛错。升级到2.6以后,项目里如果存在循环依赖,启动直接失败。别急着开allow-circular-references开关,先审视依赖结构,把循环拆掉。这个默认行为其实是在逼着代码变好。

还有一个很容易踩的坑:忘了注册BeanPostProcessor本身也是一个Bean。Spring会提前把实现了BeanPostProcessor接口的类创建好并注册到容器里,之后才创建普通Bean。所以如果你用@Bean方式注册自定义后处理器,最好声明为static方法,否则配置类可能被提前实例化,导致一些底层功能来不及注册。

7.3 学习建议:手写一个简化版Spring

我的建议一直很朴素:想彻底搞懂Bean创建过程,就自己手写一个极简容器。不需要实现全量功能,但要把这几件事做出来:

  • 一个BeanDefinition类,放beanClass、scope、属性值。
  • 一个BeanFactory类,内部用ConcurrentHashMap存单例对象,提供register和getBean。
  • getBean里做懒加载,第一次取没有就去实例化,并执行属性注入。
  • 加一个简单的BeanPostProcessor列表,支持在初始化前后调回调。
  • 再尝试实现singletonFactories三级缓存,跑通一个setter注入的循环依赖。

做完这个,再看Spring源码里的DefaultSingletonBeanRegistry和AbstractAutowireCapableBeanFactory,你会发现自己能逐行读懂,而不是盯着花括号发呆。开源的Spring Boot商城项目、后台管理系统,其实都是同一种套路:入口是自动配置类,中间是工厂方法生产Bean,最后靠各种后处理器完成增强。理解了创建过程,再去看那些项目里自定义的BeanProcessor和FactoryBean,基本几秒钟就能猜出它们想干什么。

我个人带团队时的体会是:真正让人豁然开朗的时刻,不是背完生命周期那张表,而是亲手在一个自定义注解上写出第一个BeanPostProcessor,然后看着注入的字段被代理对象的日志打印出来。那一刻,“Spring帮我们new对象”这句话就彻底变成了“Spring替我们管理了一个对象的完整人生”。如果这篇能帮你把这个链条上的每一环都接上,那它就没白写。看到这里,不妨现在打开你的项目,找个FactoryBean或者AOP切面的实现类,打断点跟一遍,收获会比读十篇文章都大。

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

Python字符串操作全指南:避开隐藏的坑与性能陷阱

写 Python 的人&#xff0c;几乎每天都要跟字符串打交道。但恰恰是这个最基础的数据类型&#xff0c;藏着大量"明明能跑、结果不对"的坑。我见过不少人搜"Python 字符串长度""字符串排序""字符串比较是否相等"这类问题&#xff0c;搜到…

作者头像 李华
网站建设 2026/10/23 1:58:40

容器化+CI/CD流水线实践:上线时间从1天压缩到3分钟

一年前&#xff0c;我负责的一个业务应用&#xff0c;每次发版都要折腾一整天才能说“稳了”。早上合代码&#xff0c;中午等测试反馈&#xff0c;下午打包传包&#xff0c;晚上十点等流量低了再重启服务&#xff0c;然后盯半小时日志&#xff0c;祈祷别出幺蛾子。真正让我痛下…

作者头像 李华
网站建设 2026/10/23 1:58:32

LabVIEW连接MySQL完全指南:从环境搭建到数据管理实战

1. 数据管理需求的本质&#xff1a;为什么 LabVIEW 项目要接 MySQL1.1 当 TDMS/CSV 文件堆满硬盘之后很多做 LabVIEW 的工程师&#xff0c;一开始处理测量数据都是往 CSV 或 TDMS 里写。单台设备、单个测试项目的时候&#xff0c;这种方式确实简单&#xff0c;打开文件就能看到…

作者头像 李华
网站建设 2026/10/23 1:58:40

洛谷团队一键@所有人神器:纯前端脚本实现与踩坑指南

如果你管过洛谷团队&#xff0c;一定懂我说的这个痛&#xff1a;团队群里要发一条重要通知&#xff0c;但评论区根本没有“所有人”这个按钮。以前我管理一个一百多人的刷题团队&#xff0c;每次喊人看公告、催作业、发洛谷p14258题解&#xff0c;都得手动把成员名单从头翻到尾…

作者头像 李华
网站建设 2026/10/23 4:08:32

大数据驱动的在线教育推荐系统设计与实现拆解

前阵子有个读者丢了一个项目给我看&#xff0c;标题就叫“大数据驱动的在线教育平台智能推荐系统的设计与实现&#xff08;案例分析&#xff09;-附源码”&#xff0c;还特意标了“白嫖源码”四个字。我大概扫了一眼目录结构和核心代码&#xff0c;发现这个项目比我想象中扎实得…

作者头像 李华