

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
从IOC容器启动到Bean实例化:我踩过的3个源码坑
我刚开始看Spring源码时,总觉得“不就是读配置、造对象吗?”直到自己写了个自定义BeanFactory,才发现里面全是细节。比如IOC容器的启动流程,其实就三步:资源加载→BeanDefinition注册→Bean实例化,但每一步都藏着容易踩的坑。
先说资源加载——你肯定用过@ComponentScan
或者context:component-scan
,以为Spring会自动扫描所有包?我之前写了个com.example.service
包下的Bean,却忘了在启动类上加@ComponentScan(basePackages = "com.example")
,结果启动时找不到Bean。后来看源码里的ClassPathBeanDefinitionScanner
类,才知道Spring的资源加载是“按需扫描”:它会先解析@ComponentScan
的basePackages
属性,如果没写,就默认扫描启动类所在的包。那天我把启动类从com.example
移到了com.example.app
,没改@ComponentScan
,结果service
包的Bean全没注册上——这坑我记到现在,后来写项目时,我都会把@ComponentScan
的路径写全,或者直接用@SpringBootApplication
(它自带@ComponentScan
)。
再说说BeanDefinition注册——这一步相当于给Spring一张“Bean设计图”,里面写着Bean的类名、 scope、依赖等信息。我之前做过一个Excel导出工具,想把ExcelUtil
设为单例Bean,于是加了@Component
,结果启动时发现ExcelUtil
被创建了两次——查源码才知道,我同时在applicationContext.xml
里配置了,导致BeanDefinition
被注册了两次,Spring默认会覆盖同名的,但如果id不同,就会创建多个。后来我把xml里的配置删了,只用注解,才解决问题。其实BeanDefinition
就像餐厅的“菜单”,你点一道菜,厨房按菜单做;如果菜单上有两道同名的菜,厨房肯定懵——Spring也是一样,同名的BeanDefinition
会被覆盖,不同名的就会生成多个Bean。
最让我头疼的是Bean实例化——我之前写了个UserService
,构造器里注入了OrderService
,结果启动报错“Circular reference between UserService and OrderService”。我以为是循环依赖,于是把构造器注入改成了@Autowired
的setter注入,结果居然好了!后来看源码里的AbstractAutowireCapableBeanFactory
类的doCreateBean
方法,才明白构造器注入和setter注入的区别:构造器注入是在Bean实例化时就需要依赖Bean(因为要调用构造器),而setter注入是在实例化之后,通过set方法注入——所以如果两个Bean都是构造器注入,会形成“你等我、我等你”的死循环;而setter注入时,Spring能先创建半成品Bean(比如UserService实例化后,还没注入OrderService),放进二级缓存,等OrderService创建好再补全依赖。那天我对着doCreateBean
里的createBeanInstance
(实例化)和populateBean
(填充属性)方法看了半天,才把注入顺序理清楚——现在我写Bean时,除非必要,都会优先用setter注入或字段注入,尽量避免构造器注入的循环依赖。
AOP动态代理怎么选?我用3个真实场景帮你理清
我第一次用AOP做日志切面时,就踩了个大雷:我写了个LogAspect
,切点是execution( com.example.service..*(..))
,结果发现UserService
的getUserById
方法(实现了UserServiceInterface
)被拦截了,但OrderService
的createOrder
方法(没实现接口)没被拦截——查源码才知道,我用了Spring默认的动态代理方式:JDK代理(只代理接口方法),而OrderService
没有接口,所以JDK代理没法用,得换成CGLIB代理(通过继承目标类实现代理)。后来我在application.properties
里加了spring.aop.proxy-target-class=true
,强制用CGLIB,才解决问题。
其实JDK代理和CGLIB的区别,我用3个真实场景就能说清:
PaymentServiceInterface
,实现类是PaymentServiceImpl
,用JDK代理就行,因为JDK代理是通过java.lang.reflect.Proxy
类生成接口的实现类,性能比CGLIB好(JDK1.8之后,动态代理的性能优化了很多);CacheUtil
(没有接口),想代理它的getCache
方法,就得用CGLIB,因为CGLIB是生成目标类的子类,重写父类方法实现代理;SecurityUtil
,里面有个final checkPermission
方法,用CGLIB代理时发现这个方法没被拦截——查源码里的Enhancer
类才知道,CGLIB是通过继承实现的,final方法不能被重写,所以没法代理。那天我把final
去掉,才让切面生效。为了帮你更清楚地区分,我整理了一张对比表:
代理方式 | 底层原理 | 支持的目标 | 核心局限 | 推荐场景 |
---|---|---|---|---|
JDK代理 | 实现目标接口的代理类 | 有接口的类 | 无法代理类方法、final接口方法 | 接口驱动的项目(如SOA、微服务) |
CGLIB代理 | 继承目标类的子类 | 无接口的类、有接口的类 | 无法代理final类、final方法 | 无接口的工具类、需要代理类方法的场景 |
除了代理方式,AOP的切面执行链也是个容易懵的点。我之前做过一个“权限校验+日志记录”的切面,把权限切面的@Order(1)
设成了比日志切面的@Order(2)
小,结果日志里先打了“执行方法XX”,再打“权限校验通过”——这明显逻辑反了!后来看Spring源码里的AdvisedSupport
类,才发现切面的执行顺序是按@Order
的值从小到大来的:Order(1)
的切面会先执行。那天我把权限切面的@Order
改成1
,日志切面改成2
,才让权限校验在日志之前执行——原来AdvisedSupport
会把所有切面放进List
里,然后按Ordered
接口的getOrder()
方法排序,执行时从前往后调用。
写在最后:看Spring源码的“笨办法”,我用了3年
很多人说“看Spring源码没用,会用就行”,但我想告诉你:源码是解决问题的“终极手册”。比如去年我帮一个做CRM系统的朋友调试问题,他的@Transactional
注解没生效,查了配置、看了切点表达式都没问题,最后我让他看TransactionAspectSupport
类的invokeWithinTransaction
方法——结果发现他的方法是private
的!而Spring的AOP不管用JDK还是CGLIB,都没法代理private
方法(JDK代理只能代理接口的public方法,CGLIB能代理protected方法,但private方法会被编译器忽略)。那天他把方法改成public
,事务立刻生效了。
其实看Spring源码不用从头看,找你遇到的问题对应的类就行:比如循环依赖找DefaultSingletonBeanRegistry
,Bean实例化找AbstractAutowireCapableBeanFactory
,AOP代理找DefaultAopProxyFactory
。我通常的做法是:遇到问题先复现,然后用IDE的Debug模式跟着Spring的ApplicationContext
启动流程走,看每个getBean
、createBean
的调用栈——比如之前调试@PostConstruct
注解的执行时机,我跟着InitDestroyAnnotationBeanPostProcessor
类的postProcessBeforeInitialization
方法,才发现@PostConstruct
是在Bean实例化、属性注入之后,afterPropertiesSet
之前执行的。
如果你刚开始看Spring源码,别害怕“看不懂”——我第一次看singletonFactories
的getObject()
方法时,也对着Lambda
表达式发呆。慢慢来,找一个小问题切入,比如“Bean的生命周期有哪些步骤?”,然后跟着源码里的doCreateBean
方法走一遍:从createBeanInstance
(实例化)到populateBean
(属性注入),再到initializeBean
(初始化,执行@PostConstruct
、afterPropertiesSet
),最后到registerDisposableBeanIfNecessary
(注册销毁方法)——你会发现,原来之前背的“生命周期”不是死记硬背的,而是源码里一步一步走出来的。
如果你按我说的方法试了,比如对着源码调试了一个Bean的创建流程,或者解决了一个之前搞不定的依赖问题,欢迎回来告诉我效果!我当初就是这么“笨笨地”看源码,从“会用Spring”变成了“懂Spring”——相信你也可以。
我当初刚开始看Spring源码时,犯了个特傻的错——抱着Spring核心包的源码从头翻,从BeanFactory
接口开始,到DefaultListableBeanFactory
,再到AbstractApplicationContext
,越看越迷糊。满屏都是接口、抽象类,还有一堆带Aware
、PostProcessor
的类,根本串不起来逻辑,差点以为自己不是学Java的。直到后来帮朋友调一个循环依赖的bug,他的两个单例Bean互相注入,启动就报“Circular reference”,我才被逼着去翻DefaultSingletonBeanRegistry
这个类——就是Spring处理单例Bean的核心类,里面藏着三级缓存的逻辑。我跟着源码里的getSingleton
方法一步步看,从singletonObjects
(一级缓存,成品Bean)到earlySingletonObjects
(二级缓存,半成品)再到singletonFactories
(三级缓存,Bean工厂),才搞明白:原来Spring是把还没完成属性注入的Bean先放进三级缓存,等依赖的Bean需要时,能提前拿到这个“半成品”,避免循环死锁。那次之后我才明白,看Spring源码真的不用从头啃,跟着你遇到的问题找对应的类就行,比瞎翻效率高十倍。
比如你遇到Bean实例化的问题——比如属性注入失败、@PostConstruct
没执行,就去翻AbstractAutowireCapableBeanFactory
类里的doCreateBean
方法,这是Spring创建Bean的核心流程:先调用createBeanInstance
实例化Bean对象(就是new出对象),然后用populateBean
填充属性(比如@Autowired注入),最后调用initializeBean
执行初始化方法(比如@PostConstruct
、afterPropertiesSet
)。我之前写了个自定义Bean,@Autowired
注入的DataSource
一直是null,就是跟着这个方法Debug,才发现我把Bean的scope
设成了prototype
,而DataSource
是单例,注入时Spring没找到对应的Bean——后来把scope
改回singleton
就好了。再比如AOP代理的问题,比如JDK和CGLIB的选择逻辑,直接找DefaultAopProxyFactory
类的createAopProxy
方法,里面明明白白写着:如果目标类有接口,优先用JDK代理;没有接口或者配置了proxy-target-class=true
,就用CGLIB。还有个小技巧,你可以用IDE的Debug模式跟着Spring容器的启动流程走——比如在ApplicationContext
的refresh
方法上加个断点,启动项目后一步步往下走,看refresh
里的obtainFreshBeanFactory
(获取BeanFactory)、registerBeanPostProcessors
(注册后置处理器)、finishBeanFactoryInitialization
(初始化单例Bean)这些步骤,每一步对应的方法调用栈里,都能看到getBean
、createBean
的过程,慢慢就把逻辑串起来了。真的,别害怕源码,它就是本“问题解决手册”,你遇到的90%的Spring问题,都能在里面找到答案。
为什么我加了@Component注解,Spring却没扫描到我的Bean?
Spring的@ComponentScan默认扫描启动类所在的包及其子包。如果你的Bean在其他包(比如启动类在com.example.app,Bean在com.example.service),又没在@ComponentScan里指定basePackages,Spring就找不到。解决方法是在启动类上显式配置@ComponentScan(basePackages = “com.example”),或者用@SpringBootApplication(自带@ComponentScan,覆盖启动类所在包)。
Spring为什么能解决单例Bean的循环依赖,但原型Bean不行?
Spring通过三级缓存(singletonObjects一级缓存:成品Bean;earlySingletonObjects二级缓存:半成品Bean;singletonFactories三级缓存:Bean工厂)解决单例循环依赖——先把Bean的工厂放进三级缓存,依赖Bean创建时能提前拿到半成品Bean。但原型Bean(@Scope(“prototype”))每次getBean都会新建实例,无法缓存半成品,所以循环依赖时会直接报错。
Spring的AOP默认用JDK代理还是CGLIB?怎么切换?
Spring AOP默认优先用JDK动态代理(仅代理实现接口的类);如果目标类没有接口,或配置了spring.aop.proxy-target-class=true(Spring Boot 2.x后默认是true),会切换为CGLIB代理(通过继承目标类实现)。注意:CGLIB无法代理final类或private方法,JDK代理只能代理接口的public方法。
为什么我的@Transactional注解没生效?常见原因有哪些?
常见原因包括:
刚开始看Spring源码,应该从哪里切入比较好?
不用从头看,从具体问题入手更高效:比如循环依赖找DefaultSingletonBeanRegistry类(三级缓存逻辑);Bean实例化找AbstractAutowireCapableBeanFactory类(doCreateBean方法);AOP代理找DefaultAopProxyFactory类(代理选择逻辑)。 用IDE的Debug模式,跟着Spring容器启动流程(比如ApplicationContext的refresh方法)走,看getBean、createBean的调用栈,逐步理清逻辑。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com