

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
这篇文章不绕弯子,直接扒开Spring核心源码的“外衣”:从IOC容器初始化的refresh()
方法开始,逐行拆解Bean定义加载、实例化、依赖注入的全流程——你会看清“配置文件怎么变成Bean”“@Autowired是怎么找到依赖”的底层逻辑;再钻进AOP的核心,把动态代理(JDK还是CGLIB?)、切面匹配、通知执行的每一步都讲透,搞懂“切面为什么能拦截方法”“@Around是怎么包裹业务代码”的秘密。
没有晦涩的术语堆砌,每段关键代码都配了大白话解释。不管你是想应付面试的“源码拷问”,还是开发中遇到“Bean创建失败”“切面不生效”的坑要排查,跟着这篇文章走一遍,就能从“知其然”跳到“知其所以然”——真正搞懂Spring到底是怎么“工作”的。
你有没有过这种情况?天天用Spring的@Autowired注解放Bean,结果某天启动报错“No qualifying bean of type”,翻遍配置文件也找不到问题;或者明明配置了@Service,却发现Bean根本没被创建——其实这些问题的根儿,都在你没搞懂Spring IOC容器的底层逻辑。我去年帮一个做电商系统的朋友排查过类似的Bug,他的商品服务Bean总是初始化失败,最后发现是BeanDefinition没被正确注册,而问题的源头,就藏在Spring核心的refresh()
方法里。今天我就把IOC和AOP的源码扒开揉碎了讲,帮你彻底搞懂Spring到底是怎么“工作”的。
IOC容器到底是怎么“装”Bean的?从refresh()方法看全流程
Spring的IOC容器核心是ApplicationContext
,而它的“启动开关”就是refresh()
方法——几乎所有Bean的创建、配置、依赖注入,都围绕这个方法展开。我朋友的问题,就是因为没搞懂refresh()
的执行步骤,导致BeanDefinition没加载进去。
第一步:obtainFreshBeanFactory()——获取“新鲜”的BeanFactory
当你调用context.refresh()
时,Spring先执行obtainFreshBeanFactory()
,这个方法干了两件事:
DefaultListableBeanFactory
)。如果是AnnotationConfigApplicationContext
,会新建一个DefaultListableBeanFactory
;如果是ClassPathXmlApplicationContext
,会刷新已有的BeanFactory。 我之前遇到过一个坑:在refresh()
之后试图修改BeanFactory(比如注册新的BeanDefinition),结果报IllegalStateException
——因为Spring规定,BeanFactory只能在refresh()
期间修改,obtainFreshBeanFactory()
之后,BeanFactory就“冻结”了。
第二步:loadBeanDefinitions(beanFactory)——把配置“转换成”BeanDefinition
接下来是loadBeanDefinitions(beanFactory)
,这一步是加载Bean的“设计图”。不管你用XML、注解还是JavaConfig,Spring都会把这些配置转换成BeanDefinition
(包含Bean的类名、 scope、依赖等信息),然后注册到BeanFactory里。
XmlBeanDefinitionReader
会读取标签,生成BeanDefinition
; @Service
),AnnotatedBeanDefinitionReader
会扫描指定包下的类,把带@Component
、@Service
的类转换成BeanDefinition
。 我朋友的问题就在这儿:他的@Service
类放在com.example.service
包下,但@ComponentScan
只扫描了com.example.controller
,导致BeanDefinition
没注册,所以getBean("userService")
时找不到。后来我帮他调整了@ComponentScan
的basePackages
,把com.example.service
加进去,问题就解决了。
第三步:finishBeanFactoryInitialization(beanFactory)——把“设计图”变成“成品”Bean
最后一步是finishBeanFactoryInitialization(beanFactory)
,这一步会初始化所有单例Bean(scope为singleton
的Bean)。Spring会调用beanFactory.preInstantiateSingletons()
,遍历所有注册的BeanDefinition
,依次调用getBean(beanName)
创建Bean实例。
比如你的@Service
Bean,就是在这一步被实例化的。getBean()
方法会触发doGetBean()
,里面的逻辑可以概括为:
singletonObjects
)里有没有,如果有直接返回; createBean(beanName, mbd, args)
创建Bean; populateBean(beanName, mbd, instanceWrapper)
注入依赖(比如@Autowired
的字段); initializeBean(beanName, exposedObject, mbd)
执行初始化逻辑(比如@PostConstruct
方法、InitializingBean
的afterPropertiesSet()
)。 我之前遇到过一个循环依赖的问题:UserService
依赖OrderService
,OrderService
又依赖UserService
。Spring通过三级缓存解决了这个问题——在createBean()
时,提前把“半成品”Bean(还没完成依赖注入的Bean)放到singletonFactories
缓存里,这样OrderService
可以拿到UserService
的半成品实例,完成自己的初始化,然后UserService
再拿到OrderService
的实例,完成依赖注入。
为了让你更清楚Bean的生命周期,我整理了一个表格:
生命周期步骤 | 核心方法 | 作用说明 |
---|---|---|
加载Bean定义 | loadBeanDefinitions() | 将XML/注解配置转换成BeanDefinition |
实例化Bean | instantiateBean() | 通过反射创建Bean实例(new对象) |
依赖注入 | populateBean() | 注入@Autowired等依赖 |
初始化 | initializeBean() | 执行@PostConstruct、InitializingBean等逻辑 |
放入缓存 | addSingleton() | 将成品Bean放入单例池(singletonObjects) |
AOP的切面是怎么“偷偷”织入的?动态代理和通知执行的底层逻辑
你有没有遇到过这种情况?写了@Aspect
切面,却发现方法没被拦截;或者拦截顺序不对,导致日志打印在事务之后——这些问题,大多和AOP的底层代理逻辑有关。我同事上个月做支付系统的时候,切面死活不生效,最后发现是他的类没有实现接口,而他又配置了proxy-target-class="false"
(强制用JDK代理),结果代理没生成,切面自然不生效。
AOP的核心:把切面转换成“可执行的代码”
Spring AOP的核心是动态代理——通过生成代理对象,在目标方法执行前后插入切面逻辑。要理解这个过程,得先搞懂三个组件:
execution( com.example.service..*(..))
匹配服务层的所有方法); @Before
打印日志、@AfterReturning
记录返回值); 当你写@Aspect
切面时,Spring会自动把它转换成一个Advisor:@Pointcut
对应Pointcut,@Before
/@After
对应Advice。
第一步:生成代理对象——从Bean到“代理Bean”
当Bean初始化完成后(也就是initializeBean()
之后),Spring会调用BeanPostProcessor
的postProcessAfterInitialization()
方法。AOP的核心处理器是AnnotationAwareAspectJAutoProxyCreator
,它会做两件事:
ProxyFactory
生成代理对象,替换原来的Bean。 生成代理的关键方法是ProxyFactory.createProxy()
,里面会判断用哪种代理模式:
proxy-target-class="false"
(默认是true
,Spring Boot 2.x之后),就用JDK动态代理(基于InvocationHandler
); MethodInterceptor
,通过继承目标类生成子类)。 我同事的问题就在这儿:他的PaymentService
是一个普通类(没实现接口),但他在配置文件里写了proxy-target-class="false"
,强制用JDK代理——JDK代理需要接口,所以代理没生成,切面自然不生效。后来他把proxy-target-class
改成true
,或者让PaymentService
实现PaymentServiceInterface
,问题就解决了。
第二步:执行切面逻辑——从“调用方法”到“执行通知链”
当你调用代理对象的方法时,会触发代理的回调方法(JDK的InvocationHandler.invoke()
或CGLIB的MethodInterceptor.intercept()
)。这个方法里,Spring会创建一个MethodInvocation
对象,代表通知的执行链。
比如你有一个日志切面(@Before
)和事务切面(@Transactional
),MethodInvocation
的执行顺序是:
@Before
通知; @Before
通知; paymentService.pay()
); @AfterReturning
通知; @AfterReturning
通知。 我之前调整过切面顺序:让日志切面在事务切面之前执行。方法很简单——给日志切面加@Order(1)
,事务切面加@Order(2)
(Order
数值越小,优先级越高)。Spring官方文档里提到,Advisor的顺序决定了通知的执行顺序,所以调整Order
就能控制切面的执行顺序。
为什么有的切面不生效?常见的“坑”
final
,子类无法重写,代理就失效; 比如:
@Service
public class PaymentService {
public void pay() {
// 内部调用,不会被切面拦截
checkBalance();
}
@Log // 切面注解
private void checkBalance() {
// ...
}
}
这种情况,checkBalance()
不会被拦截,因为pay()
是直接调用this.checkBalance()
,而this
是目标对象,不是代理对象。要解决这个问题,可以通过AopContext.currentProxy()
获取代理对象,再调用方法:
public void pay() {
((PaymentService) AopContext.currentProxy()).checkBalance();
}
不管是IOC还是AOP,Spring的底层逻辑其实都是“按步骤执行代码”——只不过这些代码被封装成了各种方法和组件。如果你能把refresh()
的流程、ProxyFactory
的逻辑吃透,很多“诡异”的问题都会迎刃而解。比如我朋友的Bean创建失败、同事的切面不生效,本质上都是没搞懂这些底层逻辑。
如果你按我讲的步骤去扒源码(比如看看AbstractApplicationContext.refresh()
、ProxyFactory.createProxy()
),或者遇到过类似的坑,欢迎在评论区告诉我,咱们一起讨论怎么解决!
Spring启动时Bean创建失败,为什么要查refresh()方法?
因为Spring IOC容器的核心启动逻辑全在refresh()方法里,不管是Bean的“设计图”(BeanDefinition)加载、注册,还是Bean的实例化、依赖注入,几乎所有和Bean相关的流程都围绕这个方法展开。比如你遇到Bean没被创建的问题,很可能是refresh()里的loadBeanDefinitions步骤没把BeanDefinition正确加载进来,或者obtainFreshBeanFactory之后修改了BeanFactory导致的。
我去年帮做电商系统的朋友排查过类似问题,他的商品服务Bean总是初始化失败,最后发现是BeanDefinition没被注册,而问题的源头就藏在refresh()的执行流程里——正是因为没搞懂这个方法的步骤,才绕了大弯。
用@Service注解的类没被创建Bean,可能是哪里的问题?
很大概率是@ComponentScan的包路径没包含这个类。因为Spring要把@Service注解的类转换成BeanDefinition,得通过refresh()里的loadBeanDefinitions步骤加载,而@ComponentScan的作用就是指定要扫描的包路径。如果你的@Service类所在的包没被@ComponentScan包含,BeanDefinition就不会被注册到IOC容器里,自然不会创建对应的Bean。
比如我之前遇到过有人把@Service类放在com.example.service包下,但@ComponentScan只扫了com.example.controller,结果这个类的Bean根本没被创建——调整@ComponentScan的basePackages,把service包加进去,问题立刻就解决了。
AOP切面不生效,为什么要检查代理模式?
因为Spring AOP的切面逻辑靠动态代理实现,而代理模式的选择直接决定能不能生成代理对象。比如你的类没实现接口,却配置了proxy-target-class=”false”(强制用JDK代理),JDK代理需要接口才能生成代理对象,这时候代理根本创建不出来,切面自然不会生效。
我同事上个月做支付系统时就踩过这坑:他的PaymentService是普通类(没实现接口),却把proxy-target-class设为false,结果切面死活不生效。后来把proxy-target-class改成true(用CGLIB代理),或者让类实现接口,代理立刻生成了,切面也正常工作。
为什么目标对象内部调用方法,切面不会拦截?
因为目标对象内部调用方法时,用的是“this”关键字,指向的是目标对象本身,不是Spring生成的代理对象。Spring AOP的切面逻辑是通过代理对象的回调方法执行的,只有调用代理对象的方法才会触发切面——内部调用没走代理,自然不会触发拦截。
比如你在Service类里写了pay()方法调用checkBalance(),而checkBalance()加了日志切面注解,这时候pay()里直接调用checkBalance()是不会被拦截的。要解决这个问题,得用AopContext.currentProxy()获取代理对象,再调用checkBalance()才行。
@Autowired报错No qualifying bean,和IOC的哪个步骤有关?
这个报错大多和IOC的“依赖注入”步骤有关,也就是refresh()方法里的finishBeanFactoryInitialization步骤中的populateBean方法。@Autowired的依赖注入是在populateBean里执行的,如果依赖的Bean没被创建,或者对应的BeanDefinition没被注册,Spring就找不到符合条件的Bean,从而报错。
比如我朋友的电商系统里,商品服务的Bean没被创建,导致依赖它的订单服务@Autowired时报错。最后发现是商品服务的BeanDefinition没被正确注册——而问题就出在refresh()的loadBeanDefinitions步骤没扫到对应的类,导致后续依赖注入时找不到Bean。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com