游侠网云服务,免实名免备案服务器 游侠云域名,免实名免备案域名

统一声明:

1.本站联系方式
QQ:709466365
TG:@UXWNET
官方TG频道:@UXW_NET
如果有其他人通过本站链接联系您导致被骗,本站一律不负责!

2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET
3.免实名域名注册购买- 游侠云域名
4.免实名国外服务器购买- 游侠网云服务
Spring核心源码深度解析|IOC与AOP底层逻辑逐行拆解

这篇文章不绕弯子,直接扒开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(),这个方法干了两件事:

  • refreshBeanFactory():创建或刷新BeanFactory(比如DefaultListableBeanFactory)。如果是AnnotationConfigApplicationContext,会新建一个DefaultListableBeanFactory;如果是ClassPathXmlApplicationContext,会刷新已有的BeanFactory。
  • getBeanFactory():返回刚创建/刷新的BeanFactory。
  • 我之前遇到过一个坑:在refresh()之后试图修改BeanFactory(比如注册新的BeanDefinition),结果报IllegalStateException——因为Spring规定,BeanFactory只能在refresh()期间修改obtainFreshBeanFactory()之后,BeanFactory就“冻结”了。

    第二步:loadBeanDefinitions(beanFactory)——把配置“转换成”BeanDefinition

    接下来是loadBeanDefinitions(beanFactory),这一步是加载Bean的“设计图”。不管你用XML、注解还是JavaConfig,Spring都会把这些配置转换成BeanDefinition(包含Bean的类名、 scope、依赖等信息),然后注册到BeanFactory里。

  • 如果你用XML配置(比如),XmlBeanDefinitionReader会读取标签,生成BeanDefinition
  • 如果你用注解(比如@Service),AnnotatedBeanDefinitionReader会扫描指定包下的类,把带@Component@Service的类转换成BeanDefinition
  • 我朋友的问题就在这儿:他的@Service类放在com.example.service包下,但@ComponentScan只扫描了com.example.controller,导致BeanDefinition没注册,所以getBean("userService")时找不到。后来我帮他调整了@ComponentScanbasePackages,把com.example.service加进去,问题就解决了。

    第三步:finishBeanFactoryInitialization(beanFactory)——把“设计图”变成“成品”Bean

    最后一步是finishBeanFactoryInitialization(beanFactory),这一步会初始化所有单例Bean(scope为singleton的Bean)。Spring会调用beanFactory.preInstantiateSingletons(),遍历所有注册的BeanDefinition,依次调用getBean(beanName)创建Bean实例。

    比如你的@Service Bean,就是在这一步被实例化的。getBean()方法会触发doGetBean(),里面的逻辑可以概括为:

  • 查缓存:先看单例池(singletonObjects)里有没有,如果有直接返回;
  • 创建Bean:如果没有,调用createBean(beanName, mbd, args)创建Bean;
  • 依赖注入:创建完Bean后,调用populateBean(beanName, mbd, instanceWrapper)注入依赖(比如@Autowired的字段);
  • 初始化:调用initializeBean(beanName, exposedObject, mbd)执行初始化逻辑(比如@PostConstruct方法、InitializingBeanafterPropertiesSet())。
  • 我之前遇到过一个循环依赖的问题:UserService依赖OrderServiceOrderService又依赖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的核心是动态代理——通过生成代理对象,在目标方法执行前后插入切面逻辑。要理解这个过程,得先搞懂三个组件:

  • Pointcut(切点):匹配哪些方法需要被拦截(比如execution( com.example.service..*(..))匹配服务层的所有方法);
  • Advice(通知):拦截后要做的事情(比如@Before打印日志、@AfterReturning记录返回值);
  • Advisor(通知器):把Pointcut和Advice结合起来(比如“对服务层方法,执行日志通知”)。
  • 当你写@Aspect切面时,Spring会自动把它转换成一个Advisor:@Pointcut对应Pointcut,@Before/@After对应Advice。

    第一步:生成代理对象——从Bean到“代理Bean”

    当Bean初始化完成后(也就是initializeBean()之后),Spring会调用BeanPostProcessorpostProcessAfterInitialization()方法。AOP的核心处理器是AnnotationAwareAspectJAutoProxyCreator,它会做两件事:

  • 检查匹配:看看当前Bean是否匹配任何Advisor的Pointcut;
  • 生成代理:如果匹配,就用ProxyFactory生成代理对象,替换原来的Bean。
  • 生成代理的关键方法是ProxyFactory.createProxy(),里面会判断用哪种代理模式

  • 如果Bean实现了接口,且proxy-target-class="false"(默认是true,Spring Boot 2.x之后),就用JDK动态代理(基于InvocationHandler);
  • 否则用CGLIB动态代理(基于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的:CGLIB通过继承目标类生成代理,如果目标方法是final,子类无法重写,代理就失效;
  • 方法是private的:Spring AOP不支持拦截private方法(因为private方法无法被重写);
  • 调用自身方法:如果目标方法A调用了自身的方法B,而方法B被切面拦截,那么方法B不会被拦截——因为调用是在目标对象内部,没有经过代理对象。
  • 比如:

    @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。