一文吃透 Spring 代理模式:从静态到动态,再到 Spring AOP 底层实现
作为 Java 开发者,你一定听过 “代理模式”,它是 Spring AOP(面向切面编程)的核心底层技术。今天就用最通俗的语言 + 可运行的代码,带你从 “静态代理” 入门,一步步拆解 “JDK 动态代理”、“CGLIB 代理”,最后讲透 Spring AOP 到底用了哪种代理、怎么用,看完就能手写代理代码,理解 AOP 的底层逻辑!
一、先搞懂:代理模式到底是个啥?
代理模式的核心思想特别简单:找个 “中间人” 帮你做事,你只需要关注核心业务,杂七杂八的事交给代理来做。
生活中的例子:你想租房,不用自己挨个找房东、谈价格、签合同,找个房产中介(代理)就行 —— 你(目标对象)只需要关注 “住房子” 这个核心需求,“找房源、砍价、办手续” 这些非核心工作全由中介(代理)完成。
在代码里,代理模式主要解决:在不修改目标对象代码的前提下,给目标对象增加额外功能(比如日志记录、事务管理、权限校验)。
二、第一步:静态代理(最基础的代理方式)
1. 核心特点
- 代理类和目标类实现同一个接口;
- 代理类在编译期就已经确定(“静态” 的由来);
- 一个代理类只能代理一个目标类,代码复用性差。
2. 实战代码(租房场景)
第一步:定义核心业务接口(租房)
// 租房接口(核心业务规范)
public interface RentHouseService {
// 租房核心方法
void rentHouse(String tenantName);
}
第二步:实现目标类(房东,真正租房的人)
// 房东(目标对象):只关注核心业务——把房子租出去
public class Landlord implements RentHouseService {
@Override
public void rentHouse(String tenantName) {
System.out.println("房东把房子租给:" + tenantName);
}
}
第三步:实现代理类(房产中介)
// 房产中介(代理对象):帮房东处理额外工作
public class RentHouseProxy implements RentHouseService {
// 持有目标对象的引用(中介要知道替哪个房东办事)
private RentHouseService target;
// 通过构造器传入目标对象
public RentHouseProxy(RentHouseService target) {
this.target = target;
}
@Override
public void rentHouse(String tenantName) {
// 代理的额外功能:租房前(前置增强)
beforeRent(tenantName);
// 调用目标对象的核心方法(真正的租房逻辑)
target.rentHouse(tenantName);
// 代理的额外功能:租房后(后置增强)
afterRent(tenantName);
}
// 租房前:核验租客身份(额外功能)
private void beforeRent(String tenantName) {
System.out.println("中介核验[" + tenantName + "]的身份信息,确认无不良记录");
}
// 租房后:签订合同、收取中介费(额外功能)
private void afterRent(String tenantName) {
System.out.println("中介与[" + tenantName + "]签订租房合同,收取中介费500元");
}
}
第四步:测试代码
public class StaticProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象(房东)
RentHouseService landlord = new Landlord();
// 2. 创建代理对象(中介),关联房东
RentHouseService proxy = new RentHouseProxy(landlord);
// 3. 通过代理对象调用方法(租客找中介租房)
proxy.rentHouse("张三");
}
}
运行结果
中介核验[张三]的身份信息,确认无不良记录
房东把房子租给:张三
中介与[张三]签订租房合同,收取中介费500元
3. 静态代理的优缺点
- ✅ 优点:简单易懂,不修改目标对象代码,就能增加额外功能;
- ❌ 缺点:
- 代码冗余:每增加一个业务接口,就要写一个对应的代理类;
- 维护成本高:接口修改后,目标类和代理类都要改。
三、第二步:JDK 动态代理(解决静态代理的冗余问题)
1. 核心特点
- 代理类不需要手动编写,而是在程序运行时动态生成(“动态” 的由来);
- 目标类必须实现接口(JDK 动态代理的前提);
- 底层依赖
java.lang.reflect.Proxy和InvocationHandler接口实现。
2. 核心原理
JDK 动态代理会在运行时,根据目标类的接口,动态生成一个代理类的字节码,然后加载到 JVM 中 —— 你不用写任何代理类的代码,只需要告诉 JVM “要增强什么功能” 即可。
3. 实战代码(改造租房场景)
第一步:复用之前的接口和目标类(RentHouseService、Landlord)
不用改,直接复用。
第二步:编写动态代理处理器(核心:定义增强逻辑)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 动态代理处理器:定义额外增强的逻辑
public class RentHouseInvocationHandler implements InvocationHandler {
// 目标对象(可以是任意实现了接口的类,通用性强)
private Object target;
public RentHouseInvocationHandler(Object target) {
this.target = target;
}
/**
* 核心方法:代理对象调用任何方法时,都会执行这个方法
* @param proxy 代理对象本身(一般不用)
* @param method 目标对象被调用的方法
* @param args 目标方法的参数
* @return 目标方法的返回值
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强:租房前的身份核验
System.out.println("【JDK动态代理】核验[" + args[0] + "]的身份信息");
// 调用目标对象的核心方法(反射实现)
Object result = method.invoke(target, args);
// 后置增强:租房后的合同签订
System.out.println("【JDK动态代理】与[" + args[0] + "]签订合同,收取中介费");
return result;
}
}
第三步:编写动态代理工厂(生成代理对象)
import java.lang.reflect.Proxy;
// 代理工厂:封装生成代理对象的逻辑,方便调用
public class JdkProxyFactory {
/**
* 生成动态代理对象
* @param target 目标对象
* @return 代理对象
*/
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
// 目标对象的类加载器
target.getClass().getClassLoader(),
// 目标对象实现的所有接口(JDK动态代理的关键)
target.getClass().getInterfaces(),
// 代理处理器(定义增强逻辑)
new RentHouseInvocationHandler(target)
);
}
}
第四步:测试代码
public class JdkDynamicProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象
RentHouseService landlord = new Landlord();
// 2. 生成动态代理对象(不用写代理类,运行时生成)
RentHouseService proxy = (RentHouseService) JdkProxyFactory.getProxy(landlord);
// 3. 调用代理方法
proxy.rentHouse("李四");
// 打印代理对象的类名,验证是动态生成的
System.out.println("代理对象的类名:" + proxy.getClass().getName());
}
}
运行结果
【JDK动态代理】核验[李四]的身份信息
房东把房子租给:李四
【JDK动态代理】与[李四]签订合同,收取中介费
代理对象的类名:com.sun.proxy.$Proxy0
4. JDK 动态代理的优缺点
- ✅ 优点:
- 通用性强:一个处理器可以代理任意实现接口的类;
- 无代码冗余:不用手动写代理类;
- ❌ 缺点:目标类必须实现接口,如果目标类没有实现任何接口,就无法使用 JDK 动态代理。
四、第三步:CGLIB 代理(解决无接口的代理问题)
1. 核心特点
- 目标类不需要实现接口(弥补 JDK 动态代理的不足);
- 底层通过 ASM 字节码框架,动态生成目标类的子类作为代理类;
- 需要引入 CGLIB 依赖(Spring-core 已内置,不用单独引)。
2. 核心原理
CGLIB 通过继承目标类,生成一个子类(代理类),并重写目标类的非 final 方法 —— 调用代理类的方法时,会先执行增强逻辑,再调用父类(目标类)的方法。
注意:如果目标类的方法是 final 的,CGLIB 无法重写,也就无法代理。
3. 实战代码(无接口的目标类)
第一步:创建无接口的目标类(房东)
// 无接口的房东类(无法用JDK动态代理)
public class LandlordWithoutInterface {
// 租房方法(非final)
public void rentHouse(String tenantName) {
System.out.println("无接口房东把房子租给:" + tenantName);
}
}
第二步:编写 CGLIB 方法拦截器(定义增强逻辑)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// CGLIB方法拦截器:类似JDK的InvocationHandler
public class RentHouseMethodInterceptor implements MethodInterceptor {
// 目标对象
private Object target;
public RentHouseMethodInterceptor(Object target) {
this.target = target;
}
/**
* 代理方法执行时,会触发这个方法
* @param proxy 代理对象
* @param method 目标方法
* @param args 方法参数
* @param methodProxy 方法代理(用于调用父类方法)
* @return 目标方法返回值
* @throws Throwable 异常
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 前置增强
System.out.println("【CGLIB代理】核验[" + args[0] + "]的信用记录");
// 调用目标对象的方法(通过methodProxy调用父类方法,效率更高)
Object result = methodProxy.invokeSuper(proxy, args);
// 后置增强
System.out.println("【CGLIB代理】与[" + args[0] + "]完成押金收取");
return result;
}
}
第三步:编写 CGLIB 代理工厂
import net.sf.cglib.proxy.Enhancer;
// CGLIB代理工厂:生成代理对象
public class CglibProxyFactory {
public static Object getProxy(Object target) {
// 1. 创建增强器(核心类)
Enhancer enhancer = new Enhancer();
// 2. 设置父类(目标类)
enhancer.setSuperclass(target.getClass());
// 3. 设置方法拦截器
enhancer.setCallback(new RentHouseMethodInterceptor(target));
// 4. 创建代理对象(生成子类)
return enhancer.create();
}
}
第四步:测试代码
public class CglibProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象(无接口)
LandlordWithoutInterface landlord = new LandlordWithoutInterface();
// 2. 生成CGLIB代理对象
LandlordWithoutInterface proxy = (LandlordWithoutInterface) CglibProxyFactory.getProxy(landlord);
// 3. 调用代理方法
proxy.rentHouse("王五");
// 打印代理对象的父类,验证是目标类的子类
System.out.println("代理对象的父类:" + proxy.getClass().getSuperclass().getName());
}
}
运行结果
【CGLIB代理】核验[王五]的信用记录
无接口房东把房子租给:王五
【CGLIB代理】与[王五]完成押金收取
代理对象的父类:com.example.proxy.LandlordWithoutInterface
4. CGLIB 代理的优缺点
- ✅ 优点:无需目标类实现接口,适用范围更广;
- ❌ 缺点:
- 无法代理 final 方法(因为无法继承重写);
- 由于是继承实现,目标类的构造方法会被调用两次(增强器创建代理时)。
五、关键:Spring AOP 到底用哪种代理?
Spring AOP 是代理模式的 “集大成者”,它会自动选择代理方式,核心规则如下:
1. Spring AOP 的代理选择逻辑

2. 手动指定代理方式(Spring 配置)
如果想强制 Spring 使用 CGLIB 代理(即使目标类有接口),可以通过配置实现:
(1)XML 配置方式
(2)注解配置方式(Spring Boot)
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB
public class AopConfig {
// 切面Bean定义
}
3. Spring AOP 实战(整合代理)
下面用 Spring AOP 实现租房场景的日志增强,感受一下 Spring 封装后的便捷性:
第一步:引入 Spring AOP 依赖(Spring Boot)
org.springframework.boot
spring-boot-starter-aop
第二步:定义目标类(无需实现接口也可以)
import org.springframework.stereotype.Service;
// 目标服务类
@Service
public class RentHouseServiceImpl {
public void rentHouse(String tenantName) {
System.out.println("Spring AOP:房东把房子租给" + tenantName);
}
}
第三步:定义切面类(核心增强逻辑)
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
// 切面类:封装增强逻辑
@Aspect // 标记为切面
@Component // 交给Spring管理
public class RentHouseAspect {
// 定义切点:指定要增强的方法
@Pointcut("execution(* com.example.proxy.RentHouseServiceImpl.rentHouse(..))")
public void rentHousePointcut() {}
// 前置增强:切点前执行
@Before("rentHousePointcut() && args(tenantName)")
public void beforeRent(String tenantName) {
System.out.println("Spring AOP前置:核验" + tenantName + "的身份");
}
// 后置增强:切点后执行
@After("rentHousePointcut() && args(tenantName)")
public void afterRent(String tenantName) {
System.out.println("Spring AOP后置:与" + tenantName + "签订合同");
}
}
第四步:测试 Spring AOP
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy // 开启AOP
@ComponentScan("com.example.proxy") // 扫描包
public class SpringAopTest {
public static void main(String[] args) {
// 1. 创建Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringAopTest.class);
// 2. 获取目标对象(实际是代理对象)
RentHouseServiceImpl service = context.getBean(RentHouseServiceImpl.class);
// 3. 调用方法
service.rentHouse("赵六");
// 打印对象类型,验证是代理对象
System.out.println("对象类型:" + service.getClass().getName());
}
}
运行结果
Spring AOP前置:核验赵六的身份
Spring AOP:房东把房子租给赵六
Spring AOP后置:与赵六签订合同
对象类型:com.example.proxy.RentHouseServiceImpl$$EnhancerBySpringCGLIB$$xxxxxxx
说明:因为
RentHouseServiceImpl没有实现接口,Spring 自动用了 CGLIB 代理,所以对象类型是目标类的子类(带 CGLIB 标识)。
六、总结
核心知识点回顾
- 静态代理:手动编写代理类,实现同一接口,简单但冗余,适合简单场景;
- JDK 动态代理:运行时生成代理类,依赖接口,通用性强,Spring AOP 默认首选;
- CGLIB 代理:运行时生成目标类的子类,无需接口,弥补 JDK 代理的不足;
- Spring AOP:自动选择代理方式(有接口用 JDK,无接口用 CGLIB),可通过
proxy-target-class=true强制使用 CGLIB。
关键选型建议
- 如果目标类有接口:优先用 JDK 动态代理(符合面向接口编程思想);
- 如果目标类无接口:只能用 CGLIB 代理;
- Spring Boot 2.x 及以后:默认使用 CGLIB 代理(即使有接口),简化了配置。
代理模式是 Spring 框架的核心基础,理解它不仅能搞懂 AOP 的底层逻辑,还能在实际开发中灵活扩展业务功能(比如日志、事务、权限)。







