
其实不是你没用心,是没找对“啃”的方式——不用逐行扒源码,不用死记硬背,这篇文章把Spring最核心的原理(IOC容器初始化、AOP切面执行、循环依赖解决等)拆成了直白的逻辑链,每一步都讲清楚“为什么要这么设计”“底层是怎么运行的”;更关键的是,把面试官常问的“高频考点”直接标在了原理里,比如“bean的初始化流程有哪些扩展点”“AOP和动态代理的关系”。
不管你是想深扒Spring的底层机制,还是急着补面试短板,不用再乱翻资料——跟着这篇走,一次把Spring源码的“关键点”啃透,再也不用怕“越啃越乱”。
你有没有过这种情况?捧着Spring源码啃了半个月,结果别人问“Spring的IOC到底是什么”,你还是只能干巴巴说“控制反转”,根本讲不清楚它到底怎么帮你管对象?或者面试时被问“循环依赖怎么解决的”,你支支吾吾蹦出“三级缓存”,但压根说不明白三级缓存里存的是啥?我前阵子带的实习生小杨就栽在这——他花了三周扒Spring的AbstractApplicationContext
源码,连Bean初始化的顺序都没理清楚,某天凌晨发消息问我:“哥,为什么refresh()
方法要拆成12个步骤啊?我越看越懵。”
我当时就告诉他:“别再逐行扒源码了!Spring源码几百万行,你啃不完的。先抓住最核心的3个问题,再补细节,效率能翻3倍——这是我做了8年Java开发,踩过无数坑才摸出来的经验。”
别再逐行扒源码了!先搞懂这3个核心问题,效率翻3倍
我见过太多人学Spring源码的误区:要么抱着源码逐行读,要么死记硬背“控制反转”“面向切面”这些抽象概念,结果越学越乱。其实Spring的核心就3件事:帮你管对象(IOC)、帮你加功能(AOP)、帮你解决对象互相依赖的问题(循环依赖)。把这3件事搞懂,你再看源码,就像拿着地图找路——一眼能看到重点。
我刚学Spring的时候,也被“控制反转”绕晕过。直到做第一个电商项目,我才突然明白:IOC就是“你不用自己new对象了,让Spring帮你搞”。比如我之前写UserService
,要自己new UserDao()
,结果后来改数据库连接方式,我得把所有new UserDao()
的地方都改一遍——耦合度高得要命。后来用了Spring的IOC,我只要在UserService
上写@Autowired private UserDao userDao;
,Spring就会自动帮我创建UserDao
对象,还把它塞进UserService
里。这就是IOC的本质:把对象的控制权从你手里转给Spring,降低代码耦合度。
那Spring是怎么“管对象”的?其实就4步流程,我用大白话给你拆:
@Service
注解、applicationContext.xml
里的
标签,或者@Configuration
里的@Bean
方法; BeanDefinition
(相当于每个对象的“说明书”),里面记着这个对象的类名、要依赖的其他对象、初始化方法是什么; new
出对象(这一步叫“实例化”); @Autowired
的userDao
,Spring会从容器里找现成的UserDao
对象塞进去; @PostConstruct
注解的方法,或者init-method
配置的方法; singletonObjects
缓存(一级缓存),下次用的时候直接拿。我之前踩过一个大坑:有次写OrderService
,在@PostConstruct
方法里调用了UserService
的getUser()
方法,结果报NullPointerException
。后来查源码才发现——@PostConstruct
是在“初始化”阶段执行的,而UserService
当时还在“填属性”阶段,根本没准备好。这就是没搞懂IOC流程的代价——你以为注解随便写,其实每个注解的执行时机都有讲究。
你有没有过这种烦人的需求?比如领导让你给所有Service
的方法加日志,或者给转账方法加事务——要是每个方法都写logger.info()
或者try-catch
,代码重复得能让人崩溃。这时候AOP就派上用场了:它能在不修改原有代码的情况下,给方法“插”功能。
我举个真实例子:去年帮朋友的生鲜电商项目做优化,他们的OrderService
里,每个方法都写了logger.info("执行{}方法,参数是{}", methodName, params)
,光日志代码就占了一半。我用AOP改了之后,只写了一个切面:
@Aspect
@Component
public class LogAspect {
@Pointcut("execution( com.example.service..*(..))") // 切点:所有Service的方法
public void servicePointcut() {}
@Before("servicePointcut()") // 前置通知:方法执行前打印日志
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.info("执行{}方法,参数是{}", methodName, args);
}
}
就这几行代码,搞定了所有Service
的日志——朋友的老板看了,直夸我“会偷懒”。
那AOP是怎么“插”功能的?核心是动态代理:
UserService
实现了IUserService
),Spring就用JDK动态代理——生成一个IUserService
的实现类,把通知(比如日志)“插”到实现类里; UserService
的子类,把通知“插”到子类里。 比如你调用userService.addUser()
,其实是调用代理对象的addUser()
方法:代理对象先执行日志通知,再调用真正的UserService
对象的addUser()
。这就是AOP的本质——用代理对象包装目标对象,在目标方法执行前后加功能。
你有没有遇到过这种情况?A
依赖B
,B
又依赖A
——比如UserService
里有@Autowired private OrderService orderService;
,OrderService
里又有@Autowired private UserService userService;
。要是你自己new
对象,肯定会栈溢出(new A()
要new B()
,new B()
又要new A()
,无限循环),但Spring能搞定——靠的是三级缓存。
我用大白话给你讲三级缓存的逻辑:
() -> createEarlyBean(beanName)
)。举个循环依赖的例子(A
依赖B
,B
依赖A
):
A
:实例化A
(new A()
),然后把A
的“对象工厂”放进三级缓存; A
填属性——发现A
依赖B
,于是去创建B
; B
:实例化B
(new B()
),然后给B
填属性——发现B
依赖A
; A
:Spring先查一级缓存(没有,因为A
还没完成),再查二级缓存(也没有),最后查三级缓存——找到A
的“对象工厂”,用它生成A
的半成品对象,放进二级缓存; B
的属性:把二级缓存里的A
半成品塞给B
; B
的创建:初始化B
,把B
放进一级缓存; A
的创建:把一级缓存里的B
塞给A
,然后初始化A
,把A
放进一级缓存。这样一圈下来,A
和B
都创建完成了——这就是三级缓存的魔力。我之前面过一个候选人,他不仅讲清楚了三级缓存,还补充了一句:“如果A
需要AOP代理,三级缓存的工厂能直接生成代理后的半成品A
,这样B
拿到的就是代理后的A
,不会有问题。”——我当场给他打了满分,因为这说明他真的理解了三级缓存的作用,不是背的概念。
我把Bean生命周期的关键节点整理成了一个表格,你可以直接对着看——面试问Spring源码,90%的问题都绕不开这些点:
阶段 | 做什么 | 面试高频问法 |
---|---|---|
实例化 | 用构造器或工厂方法创建对象(new) | Spring是怎么实例化Bean的? |
属性填充 | 给对象的字段赋值(处理@Autowired/@Value) | @Autowired是在哪个阶段生效的? |
初始化 | 执行@PostConstruct、init-method或InitializingBean | Bean的初始化方法有哪些? |
销毁 | 执行@PreDestroy、destroy-method或DisposableBean | Spring是怎么销毁Bean的? |
面试问Spring源码,其实就考这10个点——我整理了高频题和标准答案
我做了5年Java面试官,问过不下200个候选人Spring源码的问题,发现不管是BAT还是中小公司,面试题就绕着10个点转——Bean生命周期、AOP实现原理、循环依赖解决、BeanFactory vs ApplicationContext、@Bean和@Component的区别、Spring事务管理原理、Spring MVC请求流程、IOC容器初始化流程、动态代理选择(JDK vs CGLIB)、Spring扩展点(比如BeanPostProcessor)。
我挑几个最常问的,给你现成的“标准答案”——不是让你背,是让你理解思路:
别背“实例化→属性填充→初始化→销毁”,要讲细节:
Spring的Bean生命周期从“读配置”开始,到“销毁”结束,核心是这5个阶段:
BeanFactory.createBean()
方法,用构造器或工厂方法创建Bean对象(对应AbstractAutowireCapableBeanFactory
的doCreateBean()
方法); BeanWrapper
给Bean的字段赋值,处理@Autowired
(由AutowiredAnnotationBeanPostProcessor
完成)、@Value
(由PropertySourcesPlaceholderConfigurer
完成); BeanPostProcessor.postProcessBeforeInitialization()
——比如AutowiredAnnotationBeanPostProcessor
会在这一步处理@Autowired
的依赖; InitializingBean.afterPropertiesSet()
(接口方法)、@PostConstruct
(注解方法)、init-method
(XML配置的方法); BeanPostProcessor.postProcessAfterInitialization()
——比如AOP的AnnotationAwareAspectJAutoProxyCreator
会在这一步生成代理对象; DisposableBean.destroy()
(接口方法)、@PreDestroy
(注解方法)、destroy-method
(XML配置的方法)。为什么要讲细节? 因为面试官想知道你是不是真懂——比如你提到BeanPostProcessor
,说明你知道Spring的扩展机制;提到AutowiredAnnotationBeanPostProcessor
,说明你知道@Autowired
是怎么工作的。我之前面过一个候选人,把这些细节讲得清清楚楚,我当场就决定录用他——因为这不是背出来的,是真的读过源码、踩过坑。
别只说“动态代理”,要讲完整流程:
AOP的核心是“生成代理对象,在目标方法执行前后插入通知”,具体分3步:
@Aspect
注解标记一个类,里面用@Pointcut
定义切点(要拦截的方法,比如“所有Service的方法”),用@Before
/@After
/@Around
定义通知(要插入的功能); AspectJAutoProxyCreator
会扫描所有@Aspect
注解的类,解析出切点和通知,生成Advisor
(切面的“包装器”); Proxy
,实现目标接口); @Before
的日志方法),再调用目标对象的方法。举个例子:你调用userService.addUser()
,其实是调用UserServiceProxy.addUser()
——代理对象先执行LogAspect.logBefore()
(前置通知),再调用UserService.addUser()
(目标方法),最后执行LogAspect.logAfter()
(后置通知)。
别只说“ApplicationContext是BeanFactory的子类”,要讲功能差异: BeanFactory
是Spring最基础的容器,只提供Bean的创建、管理、依赖注入这3个核心功能;而ApplicationContext
是BeanFactory
的“增强版”,增加了很多实用功能:
messages_zh_CN.properties
); ApplicationEventPublisher
发布事件,比如ContextRefreshedEvent
(容器刷新完成事件); BeanFactory
需要手动配置ProxyFactory
); @Autowired
自动注入依赖(BeanFactory
需要手动调用autowireBean()
); application.properties
或系统环境变量(通过Environment
接口)。我踩过的坑:刚学Spring时,我用BeanFactory
创建容器,结果发现@Autowired
根本不生效——后来查文档才知道,BeanFactory
没有自动装配的功能,得换成ApplicationContext
才行。
别只说“用AOP”,要讲清楚事务的开启、提交、回滚流程:
Spring的事务管理靠AOP+事务管理器(PlatformTransactionManager
)实现,流程是这样的:
@Transactional
注解标记方法,指定事务的传播行为为什么说不用逐行扒Spring源码?
Spring源码有几百万行,逐行啃不仅效率低,还容易陷入细节里绕不出来——比如我带的实习生小杨花三周扒AbstractApplicationContext源码,连Bean初始化的顺序都没理清楚。其实关键是先抓核心问题,比如IOC怎么帮你管对象、AOP怎么不用改代码加功能、循环依赖怎么解决,把这几个核心逻辑搞懂再看源码,就像拿着地图找路,一眼能定位到重点,不用瞎啃。
Spring的IOC到底怎么帮我管对象?
IOC的本质不是“控制反转”的口号,而是Spring帮你搞定对象的创建和依赖。比如你之前写UserService要自己new UserDao,改数据库时得全量修改——用IOC后,你只要写@Autowired private UserDao userDao,Spring会自动做这些事:先读你的配置(比如@Service注解)生成BeanDefinition(对象的“说明书”),然后实例化对象,填好@Autowired的属性,执行@PostConstruct初始化方法,最后把对象存进一级缓存,下次用直接拿,完全不用你操心new和依赖的问题。
Spring解决循环依赖的三级缓存里到底存了啥?
三级缓存其实是三个“对象仓库”:一级缓存(singletonObjects)存“成品”对象——已经完成实例化、填属性、初始化的完整Bean;二级缓存(earlySingletonObjects)存“半成品”对象——刚实例化但还没填属性、没初始化的对象;三级缓存(singletonFactories)存“对象工厂”——能生成半成品对象的方法。比如A依赖B、B依赖A时,Spring先把A的工厂放进三级缓存,创建B需要A时,就用工厂生成A的半成品放进二级缓存给B用,等A完成所有流程后再放进一级缓存,这样就解决了循环依赖。
面试问Spring源码,主要考哪些核心点?
我做了5年Java面试官,发现不管是BAT还是中小公司,面试题基本绕着10个核心点转:Bean生命周期的完整流程(比如实例化、属性填充、初始化的细节)、AOP的实现原理(动态代理怎么插通知)、循环依赖的解决机制(三级缓存的作用)、BeanFactory和ApplicationContext的区别(功能差异)、@Bean和@Component的区别(配置方式)、Spring事务的管理原理(AOP+事务管理器)、Spring MVC的请求流程、IOC容器的初始化流程、动态代理的选择(JDK vs CGLIB)、Spring的扩展点(比如BeanPostProcessor怎么用)。这些点搞懂,面试基本能应对80%的问题。
AOP和动态代理的关系到底是什么?
AOP是“不用改代码就能加功能”的思想,动态代理是实现这个思想的技术手段。比如你要给所有Service方法加日志,用AOP写个切面后,Spring会用动态代理生成代理对象——如果你的类有接口(比如UserService实现IUserService),就用JDK动态代理生成IUserService的实现类;如果没有接口,就用CGLIB生成UserService的子类。当你调用原方法时,其实是调用代理对象的方法,先执行日志通知(比如@Before的逻辑),再执行原方法,这样就不用修改原代码也能加功能。