type
Post
status
Published
date
May 4, 2019 13:33
slug
summary
本文从零拆解 Spring 两大核心机制——AOP(面向切面编程)与 IoC(控制反转)。通过餐厅、租房等生活类比讲透核心概念,手把手演示静态代理 → JDK 动态代理 → CGLIB 代理的演进路径,并亲手模拟一个迷你版 Spring AOP 和 IoC 容器。附 JDK 代理 vs CGLIB、AOP vs 拦截器对比表,帮你彻底搞懂 Spring 底层的反射 + 代理 + 设计模式组合拳。
tags
Java
category
CS Basic
icon
password
wordCount
3572
前言
本文核心看点:
- 面向切面编程(AOP)的核心概念与五大术语
- 静态代理 → JDK 动态代理 → CGLIB 代理的演进路径与代码实战
- 控制反转(IoC)的原理与手写模拟实现
- AOP 与拦截器、JDK 代理与 CGLIB 代理的对比总结
Spring 框架的两大灵魂支柱——AOP 和 IoC,几乎是每个 Java 开发者绕不开的话题。你可能在面试里被问过无数遍,也可能在项目里天天用却从没深究过它到底怎么跑起来的。
这篇文章,我们就来一次彻底的"庖丁解牛":从概念到原理,从静态代理一路撸到动态代理,再亲手模拟一个迷你版 Spring AOP 和 IoC。看完之后,下次再有人问你"Spring AOP 怎么实现的",你可以微微一笑,说:"来,我给你手写一个。"
面向切面编程(AOP)
AOP(Aspect Oriented Programming)——面向切面编程。听起来很高大上,其实核心思想朴素得像生活常识。
想象你开了一家餐厅,每道菜出锅前都要检查食材是否新鲜,出锅后都要摆盘拍照发朋友圈。这些"检查"和"拍照"跟做菜本身没关系,但每道菜都需要。如果把这些逻辑写进每道菜的制作流程里,代码(菜谱)就会臃肿不堪。
AOP 做的事情就是:把这些"横切关注点"(比如日志、权限、事务)从业务逻辑中抽离出来,统一管理,自动织入。 业务代码只管做菜,检查和拍照交给"切面"。
AOP 核心术语
在动手写代码之前,先来认识五个关键角色。把它们想象成一出舞台剧的参与者:
连接点(Join Point):舞台上所有可能被"加戏"的时间点。在 Spring AOP 中,连接点专指方法的执行。简单说,类里面每一个可以被增强的方法,都是一个连接点。
切点(Pointcut):你真正想"加戏"的那几个时间点。切点通过一组规则(AspectJ 表达式)从所有连接点中筛选出目标方法。连接点是"所有候选人",切点是"最终入选的"。
增强 / 通知(Advice):加的"戏"本身——拦截到目标方法后要执行的代码。分为:
- 前置增强(Before):方法执行前
- 后置增强(AfterReturning):方法正常返回后
- 异常增强(AfterThrowing):方法抛异常时
- 最终增强(After):无论如何都执行
- 环绕增强(Around):包裹整个方法执行
切面(Aspect):切点 + 增强 = 切面。它定义了"在哪加戏"和"加什么戏"。
织入(Weaving):把切面应用到目标对象、生成代理对象的过程。三种织入时机:
- 编译期织入(需要特殊编译器,如 AspectJ)
- 类加载期织入(需要特殊类加载器)
- 运行期动态代理织入(Spring AOP 的选择 ✅)
Join Point vs Pointcut 一句话总结: Join Point 是"所有方法执行",Pointcut 是"你关心的那些方法"。Advice 在 Join Point 上执行,而 Pointcut 决定哪些 Join Point 有资格触发 Advice。
Java 代理机制
Spring AOP 的底层魔法是 JDK 动态代理。要理解 AOP,必须先搞懂 Java 的代理模式。
代理是什么?打个比方:你想找房东租房,但房东很忙不想直接跟你谈,于是找了个中介(代理)。你跟中介打交道,中介在带你看房之前可能先验证你的信用(前置增强),看完房之后帮你砍价(后置增强)。你以为自己在跟房东对话,实际上中介在中间做了很多"手脚"。
静态代理
静态代理的套路很直接:为每一个需要增强的业务类,手动写一个代理类。
假设有一个转账服务(这就是我们的"房东"):
现在创建一个代理类("中介"),在转账前加一步身份验证:
调用方式:
这里
AccountServiceImpl.transfer() 是连接点,AccountProxy.transfer() 是切点,before() 是增强。但问题来了:如果系统里有 100 个 Service 都要加身份验证,难道要写 100 个代理类?这也太"勤劳"了。所以 Spring 选择了更聪明的方式——动态代理。
JDK 动态代理
JDK 动态代理不需要手写代理类,代理对象在运行时由 JDK 自动生成。核心是
java.lang.reflect.Proxy + InvocationHandler。Step 1 — 业务接口和实现(跟之前一样):
Step 2 — 实现
InvocationHandler,定义增强逻辑:Step 3 — 通过
Proxy.newProxyInstance() 动态生成代理对象:一个
InvocationHandler 搞定所有需要相同增强的 Service,不用再写一堆代理类了。CGLIB 动态代理
JDK 动态代理有一个硬伤:目标类必须实现接口。如果有个类就是不想实现接口呢?
CGLIB 说:"没关系,我来。" CGLIB 的思路是通过继承生成目标类的子类,子类就是代理对象。(所以目标方法不能声明为
final,否则子类无法重写。)Step 1 — 业务类(注意,没有接口):
Step 2 — 实现
MethodInterceptor:Step 3 — 通过
Enhancer.create() 生成代理:JDK 动态代理 vs CGLIB 代理
对比维度 | JDK 动态代理 | CGLIB 代理 |
实现机制 | 基于反射,生成实现目标接口的匿名类 | 基于字节码(ASM 框架),生成目标类的子类 |
是否需要接口 | ✅ 必须有接口 | ❌ 不需要,直接继承目标类 |
生成代理速度 | ⚡ 快 | 🐢 较慢(需要操作字节码) |
运行时调用速度 | JDK 7/8 后已大幅优化,与 CGLIB 持平甚至更快 | 早期版本快于 JDK,但近年进步缓慢 |
限制 | 只能代理接口方法 | 目标方法不能是 final |
Spring 默认 | ✅ 有接口时默认使用 | 无接口时自动降级使用 |
在 JDK 7/8 环境下,1 万次执行的基准测试中,JDK 动态代理性能比 CGLIB 好约 20%。Spring AOP 默认使用 JDK 动态代理,只有目标类没有实现接口时才切换到 CGLIB。
AOP 的实现原理
理解了动态代理,AOP 的实现原理就水到渠成了。AOP 本质上就是通过反射自动创建代理类,让开发者只需要关注"增强逻辑",而不用操心代理的创建过程。
下面我们手动模拟一个迷你版 Spring AOP 🔧
Step 1 — 老朋友,转账业务:
Step 2 — 定义切面抽象类(模板方法模式),提供增强的默认实现:
Step 3 — 创建具体切面类,配置切入点和增强:
Step 4 — 代理工厂(模仿 Spring 的 Bean 工厂):
Step 5 — 测试运行:
到这里,一个迷你版 Spring AOP 就跑起来了!真实的 Spring AOP 在此基础上加入了注解驱动(
@Before、@After、@Around 等)和 AspectJ 切点表达式,让切面配置更加灵活优雅。AOP 的价值
- 🔓 降低耦合:业务逻辑和横切关注点(日志、权限、事务)彻底分离
- 🧱 易于扩展:新增一个切面,不需要改动任何业务代码
- ♻️ 提高复用:一份增强逻辑,通过切点匹配即可应用到所有目标方法
AOP vs 拦截器
Spring AOP 和拦截器(Interceptor)都是"代理模式"的落地,但定位不同:
对比维度 | Spring AOP | 拦截器(Interceptor) |
作用层 | Service 层(拦截 Spring 管理的 Bean) | Controller 层(拦截 HTTP 请求 / Action) |
灵活性 | 支持 @Before、@After、@Around 等多种增强,按方法精细控制 | 链式处理,每次请求都经过整条链,灵活性相对有限 |
实现机制 | JDK 动态代理 / CGLIB | 基于 Servlet 规范的 HandlerInterceptor |
典型场景 | 事务管理、权限校验、日志记录 | 登录校验、请求日志、CORS 处理 |
简单记忆:拦截器管"请求入口",AOP 管"业务内部"。两者互补,不冲突。
控制反转(IoC)
如果说 AOP 是 Spring 的"瑞士军刀",那 IoC 就是 Spring 的"地基"。
IoC(Inversion of Control)——控制反转。名字听着玄乎,本质很简单:谁来创建对象?
传统方式:你需要一把锤子,就自己去五金店买一把(
new Hammer())。你需要一把螺丝刀,再跑一趟(new Screwdriver())。每个工人都得自己采购工具,累不累?IoC 方式:公司设了一个工具仓库(IoC 容器),所有工具预先放好。工人只需要报个名字,仓库就把工具递到手里。创建对象的主动权从"使用者"转移到了"容器"——这就是"反转"。
Spring IoC 容器通过 XML 配置或注解扫描类及其依赖关系,自动完成对象的创建和依赖注入。底层核心设计模式:工厂模式。
IoC 核心术语
依赖注入(Dependency Injection,DI)
传统开发中,对象 A 需要合作对象 B 时,A 自己
new B() —— A 主动创建 B,两者紧耦合。
依赖注入反转了这个过程:容器负责创建 B,并"注入"给 A。A 只需要声明"我需要一个 B",不用关心 B 从哪来、怎么创建。依赖查找(Dependency Lookup,DL)
由 Martin Fowler 在 2004 年提出。核心总结:控制的什么被反转了?答:获得依赖对象的方式反转了。
IoC 实现原理
IoC 的工作流程可以浓缩为三步:
- 创建容器:一个 Map,用来存放所有 Bean
- 扫描配置:解析 XML(或注解),通过反射创建对象,塞进容器
- 按需取用:需要对象时,从容器里按 id 取出
下面手写一个迷你版 IoC 容器 🏗️
Step 1 — 定义
BeanFactory 接口和实现:Step 2 — 创建几个业务对象:
Step 3 — 使用我们的迷你 IoC:
对象的创建、管理全部交给容器,业务代码只管用。这就是 IoC 的魅力。
真实的 Spring IoC 在此基础上增加了作用域管理(singleton / prototype)、生命周期回调(
@PostConstruct / @PreDestroy)、自动装配(@Autowired)等能力,但核心思路一脉相承。总结
AOP 解决的是"横切关注点"的复用问题——把日志、事务、权限等逻辑从业务代码中抽离,通过动态代理自动织入。
IoC 解决的是"对象创建与依赖管理"的问题——将控制权从业务代码转移到容器,实现松耦合。
两者结合,构成了 Spring 框架的核心骨架:IoC 管"谁来干活",AOP 管"干活前后还要干啥"。
从静态代理到 JDK 动态代理再到 CGLIB,代理模式一路进化;从手动
new 到 IoC 容器自动注入,对象管理全面托管。理解这两个核心机制,不仅能写出更优雅的 Spring 代码,也能在排查 Bean 注入失败、事务不生效等"经典坑"时游刃有余。Spring 的魔法,说到底不过是反射 + 代理 + 设计模式的组合拳。掌握了原理,魔法就不再神秘。
