【JavaEE30-后端部分】Spring AOP 原理——代理模式,原来AOP是这样“偷偷”增强你的代码的【AI辅助理解】
开篇:你有没有想过这个问题?
老铁们,前面我们学了怎么用 AOP 给方法加耗时统计、加日志、加各种增强功能。用起来是真爽——写一个切面类,加几个注解,所有方法都自动被增强了。
但是,你有没有想过一个问题:Spring 到底是怎么做到“不修改源码就能增强方法”的?
这就像你买了一台冰箱,它本来只能制冷,突然有一天它自己学会了制冰、联网、播放音乐……你没拆开它,它怎么就多了这些功能?
答案就是:代理模式。今天我们就来揭开这个黑盒子,看看 Spring AOP 背后的“魔术”到底是怎么变的。
一、什么是代理模式?先讲个故事
1.1 没有代理的日子
假设你是一个房东,手里有套房子要出租。你自己带人看房、自己谈价格、自己签合同、自己收租金……累不累?更麻烦的是,如果租客半夜马桶坏了,你也得自己去修。
这就是没有代理的情况:客户端(租客)直接访问目标对象(房东)。
租客 → 房东(看房、签合同、收租、维修)
1.2 有了代理之后
后来你找了个中介。租客要找房子,先找中介;中介带看房、谈价格、签合同、收租金;出了事,租客也先找中介。你只需要安心收钱就行了。
这就是代理模式:客户端不直接访问目标对象,而是通过一个代理类来间接访问。
租客 → 中介(代理) → 房东(目标)
中介帮你干了所有杂活,但你(房东)的核心业务(出租房子)一点都没变。这就是代理模式的核心价值:在不修改目标对象的前提下,对目标对象的功能进行增强或控制。
二、代理模式在AOP中的角色
把上面的例子对应到 AOP:
| 生活中的角色 | AOP 中的角色 | 说明 |
|---|---|---|
| 房东 | 目标对象(Target) | 真正干活的业务方法 |
| 租客 | 客户端(Client) | 调用业务方法的代码 |
| 中介 | 代理对象(Proxy) | 在目标方法前后干杂活的切面 |
| 租房合同 | 接口(Interface) | 规定房东和中介都要做的事 |
画个图就清楚了:

三、代理模式的两种实现方式
代理模式分为两种:静态代理和动态代理。它们的区别就像“提前找好中介”和“临时找个中介”。
3.1 静态代理——提前写好中介合同
定义:在程序运行前,代理类的 .class 文件就已经存在了。也就是说,你是手动写的代理类。
生活例子:你提前找好了一个中介,签了合同,以后所有租房业务都通过他。这个中介是固定存在的,不是临时找的。
代码示例(以租房为例):
// 1. 定义接口(房东和中介都要遵守的规矩)
public interface HouseSubject {
void rentHouse();
}
// 2. 目标对象(房东)
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我出租房子");
}
}
// 3. 代理对象(中介)
public class HouseProxy implements HouseSubject {
private HouseSubject target; // 被代理的房东
public HouseProxy(HouseSubject target) {
this.target = target;
}
@Override
public void rentHouse() {
System.out.println("中介:开始代理,带人看房");
target.rentHouse(); // 房东出租房子
System.out.println("中介:代理结束,收中介费");
}
}
// 4. 使用
public class Main {
public static void main(String[] args) {
HouseSubject landlord = new RealHouseSubject();
HouseProxy proxy = new HouseProxy(landlord);
proxy.rentHouse(); // 通过中介租房
}
}
运行结果:
中介:开始代理,带人看房
我是房东,我出租房子
中介:代理结束,收中介费
静态代理的缺点:
- 如果房东新增了“卖房子”的业务,接口要改,房东要改,中介也要改。
- 如果来了个新房东(新的被代理对象),又要写一个新的代理类。
- 代码写死,不灵活,维护成本高。
所以,静态代理在日常开发中几乎不用。
3.2 动态代理——运行时临时找个中介
定义:在程序运行时,动态创建代理对象。你不需要手动写代理类,JDK 或 CGLIB 帮你自动生成。
生活例子:你不是提前找好中介,而是每次有租客来看房时,临时打电话叫一个中介过来。这个中介是“现找现用”的。
动态代理有两种实现方式:JDK 动态代理和CGLIB 动态代理。
四、JDK 动态代理——只能代理有接口的类
4.1 工作原理
JDK 动态代理要求目标对象必须实现接口。它会在运行时,根据你给的接口,动态生成一个代理类。
三步走:
- 定义一个接口(目标对象要实现它)。
- 写一个
InvocationHandler,在里面写增强逻辑。 - 用
Proxy.newProxyInstance()创建代理对象。
4.2 代码示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 接口
public interface HouseSubject {
void rentHouse();
}
// 2. 目标对象(房东)
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我出租房子");
}
}
// 3. 自定义 InvocationHandler(增强逻辑写在这里)
public class JDKInvocationHandler implements InvocationHandler {
private Object target; // 被代理的对象
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理:开始代理");
Object result = method.invoke(target, args); // 调用原方法
System.out.println("JDK代理:代理结束");
return result;
}
}
// 4. 使用
public class Main {
public static void main(String[] args) {
HouseSubject landlord = new RealHouseSubject();
// 动态创建代理对象
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
landlord.getClass().getClassLoader(), // 类加载器
landlord.getClass().getInterfaces(), // 要实现的接口
new JDKInvocationHandler(landlord) // 增强逻辑
);
proxy.rentHouse();
}
}
运行结果:
JDK代理:开始代理
我是房东,我出租房子
JDK代理:代理结束
4.3 JDK 动态代理的优缺点
| 优点 | 缺点 |
|---|---|
| 不需要手动写代理类,自动生成 | 只能代理实现了接口的类 |
| 代码灵活,增强逻辑统一 | 如果目标类没有接口,无法使用 |
五、CGLIB 动态代理——没有接口也能代理
5.1 为什么需要 CGLIB?
JDK 动态代理有个致命问题:目标类必须实现接口。但很多时候,我们的业务类可能没有接口(比如直接写的 @Service、@Controller)。怎么办?
CGLIB(Code Generation Library)解决了这个问题。它通过继承目标类来生成代理子类,所以不需要接口。
生活例子:你找个中介,但房东没有签任何合同(没有接口)。CGLIB 的做法是:直接“认”这个房东当干爹,然后以“干儿子”的身份去帮他出租房子。
5.2 工作原理
CGLIB 会在运行时,动态生成目标类的子类,在子类中重写父类的方法,加入增强逻辑。
三步走:
- 写一个
MethodInterceptor,在里面写增强逻辑。 - 用
Enhancer.create()创建代理对象。
5.3 代码示例
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 1. 目标对象(没有接口!)
public class RealHouseSubject {
public void rentHouse() {
System.out.println("我是房东,我出租房子");
}
}
// 2. 自定义 MethodInterceptor
public class CGLIBInterceptor implements MethodInterceptor {
private Object target;
public CGLIBInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB代理:开始代理");
Object result = proxy.invoke(target, args); // 调用原方法
System.out.println("CGLIB代理:代理结束");
return result;
}
}
// 3. 使用
public class Main {
public static void main(String[] args) {
RealHouseSubject landlord = new RealHouseSubject();
// 动态创建代理对象(通过继承)
RealHouseSubject proxy = (RealHouseSubject) Enhancer.create(
landlord.getClass(), // 目标类的类型
new CGLIBInterceptor(landlord) // 增强逻辑
);
proxy.rentHouse();
}
}
运行结果:
CGLIB代理:开始代理
我是房东,我出租房子
CGLIB代理:代理结束
5.4 CGLIB 的优缺点
| 优点 | 缺点 |
|---|---|
| 不需要接口,可以代理普通类 | 不能代理 final 类(因为 final 不能被继承) |
| 性能较好 | 需要额外引入 CGLIB 依赖(Spring Boot 已内置) |
六、JDK vs CGLIB:一张图看懂区别

七、Spring AOP 到底用哪种代理?
Spring AOP 的代理选择逻辑很简单:
- 目标类实现了接口 → 默认使用 JDK 动态代理
- 目标类没有实现接口 → 使用 CGLIB 动态代理
Spring Boot 2.x 开始,为了简化配置,默认强制使用 CGLIB 代理(不管你有没有接口)。这样更统一,也更方便。
如果你想改回去,可以在 application.properties 中配置:
spring.aop.proxy-target-class=false
但一般不建议改,因为 CGLIB 更通用。
八、结语:AOP 的学习到此结束
老铁们,到这儿,我们的 Spring AOP 系列就全部结束了。
我们一路走来:
- 入门篇:用 AOP 统计方法耗时,感受它的威力。
- 核心概念篇:把切点、连接点、通知、切面这些抽象概念变得具体化。
- 通知类型篇:搞清楚了五种通知的执行时机和顺序。
- 切点表达式篇:学会了用
execution和@annotation精准定位要增强的方法。 - 原理篇:揭开了 AOP 的神秘面纱,明白了它背后是动态代理在支撑。
AOP 的核心思想就一句话:把重复的、横跨多个模块的共性功能抽取出来,在不修改源码的情况下,统一增强。
掌握了 AOP,你就掌握了一种强大的代码复用和功能增强的能力。以后遇到“给所有方法加日志”“统计所有方法耗时”“统一权限校验”这类需求,你就能轻松搞定。
Spring AOP 就到这里,感谢你的陪伴!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)