大家好!今天我们来聊一个在 Java 开发中非常实用但又容易让人混淆的话题 - 动态代理。无论你是在做框架开发,还是使用 Spring、Hibernate 等框架,动态代理都在背后默默工作。特别是在 AOP(面向切面编程)中,动态代理可以说是核心技术了。
Java 中主要有两种动态代理实现方式:JDK 自带的动态代理和 CGLIB 动态代理。它们各有特点,今天我们就通过实例深入分析,带你真正理解这两种技术。
什么是代理模式?
在讲动态代理前,我们先简单理解一下什么是代理模式。
简单来说,代理模式就是在调用者和实际对象之间加了一个"中间人"。这个中间人可以在不改变原始对象代码的情况下,增强原始对象的功能。比如添加日志、事务控制、权限验证等。
静态代理与动态代理的区别
静态代理 需要我们手动编写代理类,在编译期就确定了代理与被代理的关系。而动态代理则是在运行时动态生成代理类,无需手动编写代理类代码,大大提高了灵活性和可扩展性。
JDK 动态代理:基于接口的代理方式
JDK 自带的动态代理是 Java 原生支持的,它只能代理实现了接口的类,这是它最大的限制。
工作原理
JDK 动态代理的核心是InvocationHandler
接口和Proxy
类:
- 你需要实现
InvocationHandler
接口,在其中定义代理行为 - 使用
Proxy.newProxyInstance()
创建代理对象
代理类生成机制 : JDK 动态生成的代理类是java.lang.reflect.Proxy
的子类,同时实现了目标接口。这也解释了为什么 JDK 代理只能转换为接口类型,而不能转换为目标类型 - 因为代理对象实际上是 Proxy 的子类,与目标类没有继承关系。
Proxy.newProxyInstance()
方法有三个关键参数:
- ClassLoader loader:用于定义代理类的类加载器,确保代理类与目标接口在同一类加载空间
- Class<?>[] interfaces:代理类需要实现的接口列表,决定了代理对象的类型和可用方法
- InvocationHandler h:方法调用处理器,包含代理的实际行为逻辑
实战案例
假设我们有一个计算器接口和实现:
java
// 计算器接口
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
// 实际实现
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
现在我们想在每次计算前后添加日志,不修改原代码的情况下,可以这样用 JDK 动态代理:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
public static void main(String[] args) {
// 创建真实对象
Calculator realCalculator = new CalculatorImpl();
// 创建调用处理器
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK代理]方法执行前: " + method.getName());
// 执行真实对象的方法
Object result = method.invoke(realCalculator, args);
System.out.println("[JDK代理]方法执行后: " + method.getName() + ", 结果: " + result);
return result;
}
};
// 创建代理对象
Calculator proxyCalculator = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class[]{Calculator.class},
handler);
// 通过代理对象调用方法
int addResult = proxyCalculator.add(5, 3);
System.out.println("最终结果: " + addResult);
int subtractResult = proxyCalculator.subtract(5, 3);
System.out.println("最终结果: " + subtractResult);
}
}
运行结果:
csharp
[JDK代理]方法执行前: add
[JDK代理]方法执行后: add, 结果: 8
最终结果: 8
[JDK代理]方法执行前: subtract
[JDK代理]方法执行后: subtract, 结果: 2
最终结果: 2
JDK 动态代理的局限性
- 必须有接口:被代理的类必须实现至少一个接口
- 只能代理接口方法:非接口方法无法被代理
- 代理类型转换限制:只能转换为代理的接口类型,不能转换为被代理类的类型
CGLIB 动态代理:基于继承的代理方式
CGLIB (Code Generation Library) 是一个强大的高性能代码生成库,其工作原理是通过继承被代理类生成子类,所以它可以代理没有接口的类。
工作原理
CGLIB 动态代理的核心是MethodInterceptor
接口:
- 实现
MethodInterceptor
接口定义拦截行为 - 使用
Enhancer
类创建代理对象
实战案例
首先,需要添加 CGLIB 依赖(如果使用 Spring,已经包含了 CGLIB):
Maven:
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
现在我们创建一个没有实现接口的类:
java
// 没有实现接口的类
public class MathCalculator {
public int multiply(int a, int b) {
return a * b;
}
public int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("除数不能为0");
return a / b;
}
}
使用 CGLIB 创建代理:
java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyDemo {
public static void main(String[] args) {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(MathCalculator.class);
// 设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB代理]方法执行前: " + method.getName());
// 调用原始方法 - 注意这里使用的是invokeSuper而非反射调用
Object result = proxy.invokeSuper(obj, args);
System.out.println("[CGLIB代理]方法执行后: " + method.getName() + ", 结果: " + result);
return result;
}
});
// 创建代理对象
MathCalculator proxyCalculator = (MathCalculator) enhancer.create();
// 通过代理对象调用方法
int multiplyResult = proxyCalculator.multiply(5, 3);
System.out.println("最终结果: " + multiplyResult);
int divideResult = proxyCalculator.divide(10, 2);
System.out.println("最终结果: " + divideResult);
}
}
proxy.invokeSuper vs method.invoke 性能差异
CGLIB 的MethodProxy.invokeSuper
和反射的Method.invoke
有显著性能差异:
method.invoke
使用 Java 反射 API 调用方法,需要进行安全检查和参数包装proxy.invokeSuper
使用 CGLIB 的 Fast Method 技术,生成了直接调用父类方法的字节码,避免了反射调用的开销- 在高频调用场景下,
invokeSuper
的性能通常比反射调用高 5-10 倍
运行结果:
makefile
[CGLIB代理]方法执行前: multiply
[CGLIB代理]方法执行后: multiply, 结果: 15
最终结果: 15
[CGLIB代理]方法执行前: divide
[CGLIB代理]方法执行后: divide, 结果: 5
最终结果: 5
CGLIB 的优势与局限性
优势:
- 无需接口:可以代理没有实现接口的类
- 更广泛的应用场景:几乎可以代理任何类
- 更高的方法调用性能:方法调用时比 JDK 代理更高效
局限性:
- 无法代理 final 类:CGLIB 是通过继承实现的,而 final 类不能被继承
- 无法代理 final 方法:final 方法不能被重写
- 构造函数调用:创建代理对象时会调用父类构造函数,需要确保有可访问的构造函数
- 必须引入额外依赖:需要引入 CGLIB 库(虽然 Spring 已包含)
- 继承带来的复杂性:代理类是目标类的子类,可能引入类继承层级的复杂性,而 JDK 代理通过接口实现更松耦合
两种代理方式的性能比较
-
创建代理对象:
- JDK 动态代理创建代理对象速度较快
- CGLIB 生成子类需要更多时间
-
方法调用:
- JDK 代理使用反射机制调用方法,有一定性能开销
- CGLIB 通过子类直接调用方法,避免了反射开销
- 早期版本中 CGLIB 性能明显优于 JDK
- JDK 8 以后,两者性能差距缩小
- 当调用次数增加时,CGLIB 通常表现更好
如何选择 JDK 代理还是 CGLIB 代理?
使用 JDK 动态代理的情况
- 被代理类已经实现了接口
- 只需要代理接口中定义的方法
- 在 JDK 8+环境中性能已经足够好
使用 CGLIB 的情况
- 被代理类没有实现接口
- 需要代理类中的所有方法,包括非接口方法
- 对性能有极高要求的场景(方法会被频繁调用)
Spring AOP 中的选择逻辑
Spring 框架的选择逻辑非常值得借鉴:
- 如果目标对象实现了接口,默认使用 JDK 动态代理
- 如果目标对象没有实现接口,则使用 CGLIB 代理
- 可以强制使用 CGLIB(设置
proxy-target-class="true"
)
在 Spring 中,@Configuration
类的特殊处理也使用了 CGLIB 代理:
- Spring 通过 CGLIB 代理
@Configuration
类 - 这允许 Spring 在调用同一配置类中的
@Bean
方法时返回同一实例(单例行为) - 解决了 Bean 之间的循环依赖问题
实际应用场景
1. 事务管理
事务管理是代理模式最典型的应用场景之一。下面是一个更贴近实际的事务管理实现:
java
import java.sql.Connection;
import java.sql.SQLException;
// 数据库操作接口
public interface UserDao {
void save(User user) throws SQLException;
User findById(int id) throws SQLException;
}
// 实现类
public class UserDaoImpl implements UserDao {
@Override
public void save(User user) throws SQLException {
System.out.println("保存用户: " + user.getName());
}
@Override
public User findById(int id) throws SQLException {
System.out.println("查找用户ID: " + id);
return new User(id, "用户" + id);
}
}
// 使用JDK代理实现事务
public class TransactionProxy {
public static UserDao createProxy(final UserDao target) {
return (UserDao) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Connection conn = null;
try {
// 获取数据库连接
conn = DataSourceUtils.getConnection();
// 开启事务
conn.setAutoCommit(false);
System.out.println("开始事务...");
// 执行实际方法
Object result = method.invoke(target, args);
// 提交事务
conn.commit();
System.out.println("提交事务...");
return result;
} catch (Exception e) {
// 发生异常,回滚事务
if (conn != null) {
try {
conn.rollback();
System.out.println("回滚事务...");
} catch (SQLException ex) {
System.err.println("回滚事务失败: " + ex.getMessage());
}
}
throw e;
} finally {
// 释放连接
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.err.println("关闭连接失败: " + e.getMessage());
}
}
}
}
});
}
}
// 使用 - 更现代的try-with-resources写法
public class TransactionProxyJava7 {
public static UserDao createProxy(final UserDao target) {
return (UserDao) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try (Connection conn = DataSourceUtils.getConnection()) {
conn.setAutoCommit(false);
System.out.println("开始事务...");
try {
Object result = method.invoke(target, args);
conn.commit();
System.out.println("提交事务...");
return result;
} catch (Exception e) {
conn.rollback();
System.out.println("回滚事务...");
throw e;
}
}
}
});
}
}
// 使用
UserDao dao = TransactionProxy.createProxy(new UserDaoImpl());
dao.save(new User(1, "张三"));
2. 方法执行时间统计
监控每个方法的执行时间是性能分析的重要手段:
java
// CGLIB实现的性能监控代理
public class PerformanceProxy {
public static <T> T createProxy(Class<T> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
long start = System.currentTimeMillis();
Object result = proxy.invokeSuper(obj, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + "方法执行时间: " + (end - start) + "ms");
return result;
}
});
return (T) enhancer.create();
}
}
// 使用
MathCalculator calculator = PerformanceProxy.createProxy(MathCalculator.class);
calculator.multiply(999, 1001);
常见问题与解决方案
1. ClassCastException 异常
问题:使用 JDK 动态代理时,尝试将代理对象转换为目标类型而不是接口类型。
java
Calculator realCalculator = new CalculatorImpl();
// ... 创建代理
Calculator proxyCalculator = (Calculator) Proxy.newProxyInstance(...);
// 错误做法 - 会抛出ClassCastException
CalculatorImpl impl = (CalculatorImpl) proxyCalculator; // 错误!
解决方案:
- 只转换为接口类型
- 如果必须转换为实现类类型,改用 CGLIB 代理
2. "final"方法或类无法被代理
问题:CGLIB 无法代理 final 类或方法。
解决方案:
- 移除 final 修饰符(如果可以修改源码)
- 使用组合设计模式而不是继承
- 为类创建接口,并使用 JDK 动态代理
3. "self-invocation"问题
问题:当目标对象内部方法调用自己的其他方法时,这些内部调用不会被代理拦截。
java
public class ServiceImpl implements Service {
public void methodA() {
System.out.println("Method A");
// 这个调用不会被代理!
this.methodB();
}
public void methodB() {
System.out.println("Method B");
}
}
为了更清晰地理解这个问题,下面展示 CGLIB 代理也存在相同的自调用问题:
java
// CGLIB代理示例
public class SelfInvocationDemo {
public static void main(String[] args) {
ServiceImpl service = new ServiceImpl();
// 创建CGLIB代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ServiceImpl.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("进入方法: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("离开方法: " + method.getName());
return result;
}
});
ServiceImpl proxyService = (ServiceImpl) enhancer.create();
// 调用methodA会间接调用methodB,但methodB的调用不会被拦截
proxyService.methodA();
}
}
// 输出结果:
// 进入方法: methodA
// Method A
// Method B <-- 注意这里没有拦截日志
// 离开方法: methodA
解决方案:
- 避免直接使用 this 调用
- A 通过 AOP 框架获取代理对象(例如 Spring 的 AopContext)
- 重构代码移除自调用
4. 多层代理性能问题
问题:在一个对象上应用多个代理层会导致性能下降。
解决方案:
- 合并多个代理为一个
- 使用责任链模式重构
- 使用专业的 AOP 框架(如 AspectJ)
总结比较
特性 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
原理 | 基于接口 | 基于继承 |
限制条件 | 必须实现接口 | 不能是 final 类/方法 |
性能 | JDK 8 后已优化 | 方法调用性能略优 |
创建速度 | 较快 | 较慢 |
方法拦截范围 | 仅接口方法 | 所有非 final 方法 |
适用场景 | 所有实现了接口的类 | 几乎所有非 final 类 |
便捷性 | JDK 自带 | 需要额外依赖 |
耦合性 | 接口实现,松耦合 | 继承实现,紧耦合 |
结语
动态代理是 Java 中非常强大的技术,掌握了 JDK 代理和 CGLIB 代理的区别和使用方法,你就能更好地理解 Spring AOP 等框架的实现原理,也能在自己的项目中灵活运用这一技术。
记住,选择代理方式应该基于你的实际需求:
- 如果目标类实现了接口,优先考虑 JDK 动态代理
- 如果目标类没有实现接口或需要代理所有方法,考虑 CGLIB 代理
- 在特别注重性能的场景,可以通过基准测试来确定最佳选择
感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!
如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~