Spring框架中的动态代理是实现AOP(面向切面编程)的核心技术之一,它允许在运行时动态创建代理对象,从而实现对目标对象的增强。
一、动态代理概述
动态代理是一种在运行时动态创建代理对象的技术,无需为每个被代理类编写具体的代理类代码。Spring主要使用两种动态代理方式:
- JDK动态代理:基于接口的代理
- CGLIB代理:基于类继承的代理
1.1现实开发中的痛点场景
场景1:日志记录需求
假设你有50个Service类,现在需要为每个方法添加执行日志:
- 传统做法:需要修改50个类,添加重复的日志代码
- 使用动态代理:只需编写1个日志切面,统一处理所有方法
场景2:权限控制
系统有100个API接口需要添加权限检查:
- 传统做法:在每个方法开始处添加
if(!hasPermission()) return;
- 动态代理方案:统一权限拦截器,配置需要拦截的方法
1.2动态代理的核心价值
1. 开闭原则的最佳实践
- 不修改原有代码(对扩展开放)
- 不影响现有功能(对修改封闭)
- 符合SOLID设计原则
2. 解决"交叉关注点"问题
横切关注点(Cross-cutting concerns)的处理:
[传统写法]
Controller -> Service1(业务+日志+事务+安全)
Controller -> Service2(业务+日志+事务+安全)
...
[代理方案]
Controller -> Proxy -> (日志+事务+安全) -> 纯净的Service
3. 配置优于编码
可以通过配置决定哪些类/方法需要增强:
// 只需配置切点表达式,不需要修改业务代码
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) {
// 统一的日志处理
}
1.3传统硬编码方式 vs 动态代理
维度 | 传统方式 | 动态代理方案 |
---|---|---|
代码侵入性 | 需要修改业务类 | 不修改业务类 |
维护成本 | 每个类单独维护 | 集中维护 |
一致性 | 容易出现不一致实现 | 统一处理保证一致 |
功能调整 | 需要修改所有相关类 | 只需修改代理逻辑 |
新功能添加 | 需要修改大量现有代码 | 添加新切面即可 |
代码量 | 随业务类增长线性增加 | 固定代码量+配置 |
二、JDK动态代理
工作原理
JDK动态代理通过java.lang.reflect.Proxy
类和InvocationHandler
接口实现,要求被代理的类必须实现至少一个接口。
实现步骤
- 创建
InvocationHandler
实现类 - 使用
Proxy.newProxyInstance()
方法创建代理对象
示例代码
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用户");
}
}
public class JdkProxyHandler implements InvocationHandler {
private Object target;
public JdkProxyHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置处理");
Object result = method.invoke(target, args);
System.out.println("后置处理");
return result;
}
public static Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkProxyHandler(target)
);
}
}
// 使用
UserService proxy = (UserService) JdkProxyHandler.createProxy(new UserServiceImpl());
proxy.save();
特点
- 只能代理接口方法
- 性能较好
- 无需第三方库支持
三、CGLIB动态代理
工作原理
CGLIB(Code Generation Library)通过继承目标类并在子类中重写方法来实现代理,不要求目标类实现接口。
实现步骤
- 创建
MethodInterceptor
实现类 - 使用
Enhancer
类创建代理对象
示例代码
public class UserService {
public void save() {
System.out.println("保存用户");
}
}
public class CglibProxyInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置处理");
return result;
}
public static Object createProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new CglibProxyInterceptor());
return enhancer.create();
}
}
// 使用
UserService proxy = (UserService) CglibProxyInterceptor.createProxy(UserService.class);
proxy.save();
特点
- 可以代理普通类
- 通过继承方式实现,不能代理final类和方法
- 性能略低于JDK动态代理
- 需要引入CGLIB库
四、Spring中的代理选择机制
Spring AOP根据以下规则自动选择代理方式:
- 如果目标对象实现了接口,默认使用JDK动态代理
- 如果目标对象没有实现接口,使用CGLIB代理
- 可以通过配置强制使用CGLIB代理:
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
@EnableAspectJAutoProxy(proxyTargetClass = true)
五、动态代理的应用场景
- AOP实现:事务管理、日志记录、性能监控等
- 远程方法调用(RPC):如RMI、Hessian等
- 延迟加载:Hibernate的延迟加载实现
- 权限控制:方法调用前的权限检查
- 缓存:方法结果缓存
六、性能比较
- 创建速度:JDK动态代理比CGLIB快
- 执行速度:JDK动态代理1.6及以后版本与CGLIB相当,甚至更快
- 内存占用:CGLIB生成的类会占用更多永久代内存(Java 8之前)
七、选择策略
- 如果目标类实现了接口,优先使用JDK动态代理
- 如果需要代理没有接口的类,使用CGLIB
- 对于final类或方法,无法使用CGLIB代理
- 考虑使用Spring AOP的自动代理机制,而非手动创建代理
八、AOP、动态代理与反射的关系
1.回顾一下三者的基本概念
1.1. AOP (面向切面编程)
一种编程范式,将横切关注点(如日志、事务、安全等)与核心业务逻辑分离,通过"切面"模块化这些横切功能。
1.2. 动态代理
在运行时动态创建代理对象的技术,分为:
- JDK动态代理:基于接口
- CGLIB代理:基于继承
1.3. 反射(Reflection)
Java提供的在运行时检查/修改类、方法、字段等元信息的能力,可以动态调用方法和构造对象。
2.三者关系图解
[反射] ← 提供基础能力支持
↓
[动态代理] ← 利用反射实现代理逻辑
↓
[AOP] ← 基于动态代理实现切面编程
3.具体关系分析
3.1. 动态代理依赖反射实现
-
JDK动态代理:
Proxy.newProxyInstance()
内部使用反射创建代理类InvocationHandler.invoke()
通过反射调用目标方法(Method.invoke())
-
CGLIB代理:
- 使用
MethodProxy
(基于反射优化)调用父类方法 - 通过反射生成子类字节码
- 使用
3.2. AOP基于动态代理实现
Spring AOP的实现机制:
-
代理创建阶段:
- 使用动态代理技术创建代理对象
- 选择JDK或CGLIB取决于目标对象特性
-
方法调用阶段:
- 代理对象拦截方法调用
- 通过反射机制执行连接点(JoinPoint)
- 按顺序应用通知(Advice)
3.3. 反射在AOP中的关键作用
- 定位切点:通过反射分析类的方法元数据
- 方法调用:反射调用目标方法和通知方法
- 注解处理:解析@Aspect、@Pointcut等注解
4.工作流程示例(Spring AOP)
-
初始化阶段:
- 通过反射扫描@Aspect组件
- 解析切点表达式,匹配目标方法
-
代理创建:
// 伪代码展示核心逻辑 if(目标实现了接口) { 使用JDK动态代理创建代理对象; } else { 使用CGLIB生成子类代理; }
-
方法拦截:
// InvocationHandler或MethodInterceptor中 public Object invoke(...) { // 反射获取方法元信息 Method method = target.getClass().getMethod(...); // 执行前置通知(@Before) for(Advice advice : beforeAdvices) { advice.getMethod().invoke(advice.getAspect()); // 反射调用 } // 反射调用目标方法 Object result = method.invoke(target, args); // 执行后置通知(@After) // ... return result; }
九、InvocationHandler 详解
InvocationHandler
是 Java 动态代理机制中的核心接口,用于定义代理对象的方法调用行为。它是 JDK 动态代理的关键组成部分,允许开发者在运行时动态处理代理对象的方法调用。
1.基本概念
1.1. 定义
InvocationHandler
是 java.lang.reflect
包中的一个接口,只有一个方法:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
1.2. 作用
当通过代理对象调用方法时,调用会被转发到 InvocationHandler
的 invoke
方法,由它决定如何处理这次调用。
2.核心方法解析
invoke
方法参数说明
参数 | 类型 | 说明 |
---|---|---|
proxy | Object | 生成的代理对象本身(通常不直接使用,避免递归调用) |
method | Method | 被调用的方法对象,包含方法的所有元信息 |
args | Object[] | 方法调用时传入的参数数组,如果没有参数则为 null |
返回值
- 返回被代理方法的执行结果
- 如果方法返回类型是 void,则返回 null
3.工作原理
- 代理对象接收到方法调用
- 调用被转发到关联的
InvocationHandler
invoke
方法被执行- 开发者可以在
invoke
方法中:- 添加前置逻辑
- 决定是否调用原始方法
- 修改参数或返回值
- 添加后置逻辑
- 返回最终结果给调用者
4.使用示例
基础实现示例
public class MyInvocationHandler implements InvocationHandler {
private Object target; // 被代理的真实对象
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置处理
System.out.println("Before method: " + method.getName());
// 调用真实对象的方法
Object result = method.invoke(target, args);
// 后置处理
System.out.println("After method: " + method.getName());
return result;
}
}
创建代理对象
// 真实对象
RealSubject real = new RealSubject();
// 创建InvocationHandler
InvocationHandler handler = new MyInvocationHandler(real);
// 创建代理对象
Subject proxy = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(),
handler
);
// 使用代理对象
proxy.doSomething();
5.高级应用场景
1. 权限控制
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isAnnotationPresent(RequiresAuth.class)) {
checkAuthentication();
}
return method.invoke(target, args);
}
2. 性能监控
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long duration = System.currentTimeMillis() - start;
logMethodPerformance(method.getName(), duration);
return result;
}
3. 缓存机制
private Map<String, Object> cache = new ConcurrentHashMap<>();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String cacheKey = generateCacheKey(method, args);
if (cache.containsKey(cacheKey)) {
return cache.get(cacheKey);
}
Object result = method.invoke(target, args);
cache.put(cacheKey, result);
return result;
}
6.注意事项
-
递归调用问题:
- 避免在
invoke
方法中通过proxy
参数再次调用方法,会导致无限递归 - 正确做法是调用
target
对象的方法
- 避免在
-
equals/hashCode/toString:
- 代理对象默认继承自 Object 的这些方法
- 如果需要特殊处理,需要在
invoke
方法中显式判断
-
性能考虑:
- 反射调用 (
method.invoke()
) 比直接调用慢 - 对性能敏感的场景应考虑缓存 Method 对象
- 反射调用 (
-
异常处理:
- 需要妥善处理被代理方法抛出的异常
- 可以选择捕获、转换或直接抛出
7.与Spring AOP的关系
Spring AOP 的 JDK 动态代理实现中,核心类 JdkDynamicAopProxy
实现了 InvocationHandler
接口:
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
// 实现了invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Spring的代理逻辑...
}
}
十、通过比喻对 InvocationHandler 示例解析
比喻:快递代收点
想象你网购了一件商品:
- 真实对象 = 你本人(真正要收货的人)
- 代理对象 = 小区快递代收点
- InvocationHandler = 代收点的处理规则
当快递员(调用者)送货时:
- 快递员先到代收点(代理对象)
- 代收点按照预定规则(InvocationHandler)处理:
- 记录快递信息(前置处理)
- 决定是否通知你取件(是否调用真实方法)
- 可能帮你验货(修改参数/结果)
- 记录取件时间(后置处理)
分步骤代码演示
第一步:定义真实服务接口
// 银行服务接口
public interface BankService {
void deposit(double amount); // 存款
void withdraw(double amount); // 取款
double checkBalance(); // 查余额
}
第二步:实现真实服务
// 真实银行服务实现
public class RealBankService implements BankService {
private double balance = 0;
@Override
public void deposit(double amount) {
balance += amount;
System.out.println("存入: " + amount);
}
@Override
public void withdraw(double amount) {
if(balance >= amount) {
balance -= amount;
System.out.println("取出: " + amount);
} else {
System.out.println("余额不足");
}
}
@Override
public double checkBalance() {
return balance;
}
}
第三步:创建调用处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class BankServiceHandler implements InvocationHandler {
private final Object realService; // 持有真实对象
public BankServiceHandler(Object realService) {
this.realService = realService;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("╔════════════════════════════╗");
System.out.println(" 代理拦截到方法调用: " + method.getName());
// 前置处理:权限检查
if(method.getName().equals("withdraw")) {
System.out.println(" → 执行取款前的安全检查");
}
// 调用真实对象的方法
System.out.println(" ↓ 转发给真实服务处理...");
Object result = method.invoke(realService, args);
// 后置处理:记录日志
if(method.getName().equals("deposit")) {
System.out.println(" ← 存款操作已完成,更新账户流水");
}
// 特殊处理查余额
if(method.getName().equals("checkBalance")) {
System.out.println(" 当前余额查询结果: " + result);
}
System.out.println("╚════════════════════════════╝");
return result;
}
}
第四步:创建并使用代理对象
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// 1. 创建真实服务对象
BankService realBank = new RealBankService();
// 2. 创建调用处理器,传入真实对象
BankServiceHandler handler = new BankServiceHandler(realBank);
// 3. 创建代理对象
BankService proxyBank = (BankService) Proxy.newProxyInstance(
BankService.class.getClassLoader(),
new Class[]{BankService.class},
handler
);
// 4. 使用代理对象(所有调用都会被handler处理)
System.out.println("\n=== 测试存款 ===");
proxyBank.deposit(1000);
System.out.println("\n=== 测试取款 ===");
proxyBank.withdraw(500);
System.out.println("\n=== 测试查询 ===");
double balance = proxyBank.checkBalance();
System.out.println("最终余额: " + balance);
}
}
第五步:控制台输出结果
=== 测试存款 ===
╔════════════════════════════╗
代理拦截到方法调用: deposit
↓ 转发给真实服务处理...
存入: 1000.0
← 存款操作已完成,更新账户流水
╚════════════════════════════╝
=== 测试取款 ===
╔════════════════════════════╗
代理拦截到方法调用: withdraw
→ 执行取款前的安全检查
↓ 转发给真实服务处理...
取出: 500.0
╚════════════════════════════╝
=== 测试查询 ===
╔════════════════════════════╗
代理拦截到方法调用: checkBalance
↓ 转发给真实服务处理...
当前余额查询结果: 500.0
╚════════════════════════════╝
最终余额: 500.0
调用者 → 代理对象.method() → InvocationHandler.invoke() → 真实对象.method()
↑ ↓
└────── 拦截并增强处理 ───────────────┘