1. 为什么Spring源码值得你投入时间?
十年前我刚接触Spring时,也曾被那些晦涩的源码吓退。直到在某次线上事故排查中,被迫深入Spring事务源码,才发现理解底层原理带来的技术自由度有多宝贵——那次我仅用20分钟就定位到其他团队三天都没解决的分布式事务问题。
Spring作为Java生态的事实标准,其设计思想影响着几乎所有主流框架。当你真正读懂Spring源码后,会明显感受到三个层面的提升:
- 框架理解维度:MyBatis的动态代理、Spring Security的过滤器链,本质上都是Spring核心机制的延伸
- 问题排查效率:遇到诡异的事务失效、循环依赖问题时,能直接从Bean生命周期入手分析
- 架构设计能力:Spring的扩展点设计、接口抽象策略,都是可复用的架构范本
2. 高效阅读源码的准备工作
2.1 环境搭建实战
建议使用IntelliJ IDEA + Spring 5.3.x的组合(当前最稳定的生产版本),按这个步骤配置调试环境:
# 克隆Spring源码 git clone https://github.com/spring-projects/spring-framework.git git checkout v5.3.18 # 使用Gradle编译 ./gradlew :spring-oxm:compileTestJava关键技巧:
- 在
build.gradle中添加compileJava.options.compilerArgs += ['-parameters']保留参数名信息 - 为
spring-context模块添加测试类作为入口点
2.2 核心模块地图
重点关注的5个核心模块:
- spring-beans:Bean定义、依赖注入实现
- spring-context:应用上下文、事件机制
- spring-aop:动态代理、切面实现
- spring-tx:事务管理抽象
- spring-webmvc:DispatcherServlet实现
提示:初次阅读建议从
DefaultListableBeanFactory开始,这是整个IoC容器最核心的实现类
3. IoC容器深度解析
3.1 Bean生命周期全流程
通过这个测试用例可以观察完整的Bean创建过程:
public class BeanLifecycleDemo { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(MyConfig.class); context.refresh(); // 触发Bean初始化 context.close(); // 触发Bean销毁 } } @Component class MyBean implements InitializingBean, DisposableBean { @PostConstruct public void init() { System.out.println("@PostConstruct"); } @Override public void afterPropertiesSet() { System.out.println("InitializingBean"); } @PreDestroy public void preDestroy() { System.out.println("@PreDestroy"); } @Override public void destroy() { System.out.println("DisposableBean"); } }执行后会按以下顺序输出:
- 构造器调用
- @PostConstruct
- InitializingBean.afterPropertiesSet
- @PreDestroy
- DisposableBean.destroy
3.2 循环依赖的解决之道
Spring通过三级缓存巧妙解决了Setter注入的循环依赖问题:
| 缓存级别 | 存储内容 | 作用时机 |
|---|---|---|
| singletonObjects | 完整的Bean实例 | Bean初始化完成后 |
| earlySingletonObjects | 提前暴露的原始对象 | 实例化后、属性填充前 |
| singletonFactories | ObjectFactory包装的工厂对象 | 创建Bean实例时立即放入 |
典型的问题排查场景:
// 构造器注入导致的循环依赖会直接报错 @Component class ServiceA { private final ServiceB b; public ServiceA(ServiceB b) { this.b = b; } } @Component class ServiceB { private final ServiceA a; public ServiceB(ServiceA a) { this.a = a; } }经验:生产环境建议使用@Lazy延迟加载解决特殊场景的循环依赖
4. AOP实现原理剖析
4.1 代理创建流程图解
Spring动态代理的选择逻辑:
public class DefaultAopProxyFactory { public AopProxy createAopProxy(...) { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces()) { return new CglibAopProxy(config); // CGLIB代理 } return new JdkDynamicAopProxy(config); // JDK动态代理 } }4.2 拦截器链执行机制
关键源码在ReflectiveMethodInvocation.proceed():
public Object proceed() throws Throwable { if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); // 执行目标方法 } Object interceptor = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); if (interceptor instanceof MethodInterceptor) { return ((MethodInterceptor) interceptor).invoke(this); } // 其他类型拦截器处理... }常见坑点:
- 同一个切面内@Around会覆盖@Before/@After
- 自调用方法不会触发AOP(因为不走代理)
5. 事务管理底层实现
5.1 事务传播行为对照表
| 传播类型 | 源码常量 | 行为特征 |
|---|---|---|
| REQUIRED | PROPAGATION_REQUIRED | 存在事务则加入,否则新建 |
| REQUIRES_NEW | PROPAGATION_REQUIRES_NEW | 总是新建独立事务 |
| NESTED | PROPAGATION_NESTED | 创建保存点实现嵌套事务 |
| NOT_SUPPORTED | PROPAGATION_NOT_SUPPORTED | 非事务方式运行 |
5.2 事务失效的7种场景
- 方法修饰符非public:Spring AOP无法代理private方法
- 自调用问题:类内部方法互相调用不会经过代理
- 异常类型不匹配:默认只回滚RuntimeException
- 数据库引擎不支持:MyISAM引擎无事务支持
- 多数据源未指定:需要@Transactional(transactionManager="xxx")
- 传播行为配置错误:NOT_SUPPORTED会挂起已有事务
- try-catch吞异常:未将异常重新抛出
调试技巧:在TransactionInterceptor.invoke()方法打条件断点,观察事务开启/回滚逻辑
6. Spring MVC核心流程
6.1 DispatcherServlet处理时序
HttpServlet.service()入口doDispatch()核心处理:protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { HandlerExecutionChain mappedHandler = getHandler(request); HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 拦截器preHandle if (!mappedHandler.applyPreHandle(request, response)) return; // 实际处理器执行 ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler()); // 渲染视图 applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(request, response, mv); }
6.2 参数解析器妙用
自定义参数解析器示例:
public class JwtUserArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(User.class); } @Override public Object resolveArgument(...) { String token = request.getHeader("Authorization"); return JwtUtils.parseUser(token); // 实际项目需要处理各种异常情况 } } // 注册配置 @Configuration class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new JwtUserArgumentResolver()); } }7. 源码阅读进阶技巧
图形化工具辅助:
- 使用IDEA的Diagrams功能生成类图
- SequenceDiagram插件绘制调用时序
关键断点设置:
AbstractAutowireCapableBeanFactory.doCreateBean()JdkDynamicAopProxy.invoke()TransactionInterceptor.invoke()
改造源码实践: 在本地分支尝试修改Spring源码:
- 给BeanDefinition添加自定义属性
- 实现新的Scope类型
- 扩展AOP通知类型
我在团队内部推行源码阅读的实践经验是:每周组织一次"源码快闪"活动,每人用5分钟分享一个Spring类的设计亮点。这种方式既能保持持续学习,又不会给团队带来太大负担。建议从DefaultSingletonBeanRegistry这个相对独立的类开始尝试这种分享模式。