一、动态代理概述
动态代理是Java语言中一项强大的技术特性,它允许程序在运行时动态创建代理对象,而无需在编译期预先定义代理类。与静态代理相比,动态代理具有更高的灵活性和可维护性,是现代Java框架(如Spring)的核心基础之一。
1.1 静态代理 vs 动态代理
静态代理需要在编译期手动编写代理类,每个被代理类都需要对应一个代理类:
typescript
// 静态代理示例:需要为每个类编写代理类
public class UserServiceStaticProxy implements UserService {
private UserService userService;
public UserServiceStaticProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser(String name) {
System.out.println("Before method");
userService.addUser(name);
System.out.println("After method");
}
}
动态代理则在运行时动态生成代理类,大大减少了代码量:
scss
// 动态代理:一个处理器可以代理多个类
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingHandler(target)
);
1.2 Java中两种动态代理方式
Java平台主要提供两种动态代理实现机制:
- JDK动态代理 - 基于接口实现,Java原生支持
- CGLib动态代理 - 基于继承实现,需引入第三方库
这两种方式在实现原理、适用场景和性能特征上有着显著差异,下面我们将深入探讨这两种动态代理技术。
二、JDK动态代理详解
2.1 JDK动态代理的核心原理
JDK动态代理是基于接口 的代理方式。当目标类实现了至少一个接口时,可以使用JDK动态代理。其核心原理是通过反射机制在运行时创建一个实现了目标接口的代理类。 核心组件:
java.lang.reflect.Proxy- 负责生成代理类java.lang.reflect.InvocationHandler- 调用处理器,包含代理逻辑
2.2 JDK动态代理的实现机制
typescript
// 1. 定义业务接口
public interface UserService {
void addUser(String name);
void deleteUser(String name);
}
// 2. 实现类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public void deleteUser(String name) {
System.out.println("删除用户: " + name);
}
}
// 3. 实现InvocationHandler
public class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK代理] 调用方法: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("[JDK代理] 方法执行时间: " + (end - start) + "ms");
return result;
}
}
// 4. 使用代理
public class JdkProxyDemo {
public static void main(String[] args) {
UserService realService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
realService.getClass().getInterfaces(),
new LoggingHandler(realService)
);
proxy.addUser("张三");
}
}
2.3 JDK动态代理的创建流程
以下是JDK动态代理的创建过程:
具体生成代理类的反编译代码大致如下:
scala
// 反编译后的代理类示例
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m2;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void addUser(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 其他方法...
}
从反编译代码可以看出,JDK生成的代理类继承自Proxy类并实现了目标接口,这也就是为什么JDK动态代理只能基于接口而不能基于类。
三、CGLib动态代理详解
3.1 CGLib动态代理的核心原理
CGLib(Code Generation Library)是一个强大的、高性能的代码生成库,它通过继承目标类的方式来实现代理。当目标类没有实现任何接口时,可以使用CGLib动态代理。 CGLib通过在运行时创建目标类的子类,并重写其中的非final方法来实现代理功能。由于是基于继承,因此可以代理没有实现接口的普通Java类。
3.2 CGLib动态代理的实现机制
首先需要添加CGLib依赖:
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
实现CGLib代理:
typescript
// 1. 目标类(无需实现接口)
public class ProductService {
public void addProduct(String name) {
System.out.println("添加产品: " + name);
}
public String getProductInfo(String name) {
return "产品信息: " + name;
}
}
// 2. 实现MethodInterceptor
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[CGLib代理] 方法调用前: " + method.getName());
long start = System.currentTimeMillis();
// 使用invokeSuper调用父类方法
Object result = proxy.invokeSuper(obj, args);
long end = System.currentTimeMillis();
System.out.println("[CGLib代理] 方法执行时间: " + (end - start) + "ms");
return result;
}
}
// 3. 使用代理
public class CglibTest {
public static void main(String[] args) {
ProductService target = new ProductService();
CglibProxy proxyFactory = new CglibProxy();
ProductService proxy = (ProductService) proxyFactory.getInstance(target);
proxy.addProduct("笔记本电脑");
}
}
3.3 CGLib的动态代理机制
CGLib采用FastClass机制为代理类和被代理类各生成一个类,为代理类或被代理类的方法分配索引,调用时直接根据索引来执行方法,避免了使用反射调用带来的性能开销。
四、JDK代理 vs CGLib代理全面对比
4.1 原理机制对比
| 特性 | JDK动态代理 | CGLib动态代理 |
|---|---|---|
| 实现基础 | 基于接口实现 | 基于继承实现 |
| 代理目标 | 只能代理接口 | 可以代理普通类 |
| 生成方式 | 反射机制生成代理类 | 字节码技术生成子类 |
| 方法调用 | 反射调用目标方法 | 通过FastClass机制直接调用 |
4.2 性能对比
根据实际测试数据,两种代理在性能上有明显差异:
| 场景 | JDK动态代理 | CGLib动态代理 |
|---|---|---|
| 创建速度 | 较快(反射生成) | 较慢(字节码生成,约慢8倍) |
| 执行速度 | 较慢(反射调用) | 较快(直接调用,约快10倍) |
| 内存占用 | 较小 | 较大(生成子类) |
4.3 使用限制对比
| 限制方面 | JDK动态代理 | CGLib动态代理 |
|---|---|---|
| 接口要求 | 必须实现接口 | 无需接口 |
| final限制 | 无限制 | 不能代理final类和方法 |
| 构造器要求 | 无特殊要求 | 需要默认构造器 |
五、Spring框架中的代理选择机制
Spring框架根据目标类的特性自动选择代理方式:
在实际开发中,可以强制Spring使用CGLib代理:
xml
<!-- 强制使用CGLib代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
六、实际应用场景与最佳实践
6.1 选择建议
-
优先使用JDK动态代理的场景:
- 目标对象已经实现了接口
- 需要频繁创建和销毁代理对象
- 对启动性能要求较高
-
优先使用CGLib动态代理的场景:
- 目标对象没有实现接口
- 单例对象,不需要频繁创建代理
- 对运行时性能要求较高
6.2 性能优化建议
- 对于单例对象,使用CGLib代理更合适,因为执行性能更高
- 对于频繁创建销毁的对象,使用JDK动态代理,因为创建开销较小
- 缓存代理对象,避免重复创建带来的性能开销
- 合理使用条件判断,避免不必要的代理逻辑
6.3 综合示例:日志记录与性能监控
java
// 综合代理工厂,根据条件选择代理方式
public class ProxyFactory {
public static Object getProxy(Object target) {
// 如果有接口,使用JDK代理;否则使用CGLib代理
if (target.getClass().getInterfaces().length > 0) {
return createJdkProxy(target);
} else {
return createCglibProxy(target);
}
}
private static Object createJdkProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingInvocationHandler(target)
);
}
private static Object createCglibProxy(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new LoggingMethodInterceptor(target));
return enhancer.create();
}
}
// JDK代理处理器
class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 统一的预处理逻辑
long start = System.currentTimeMillis();
System.out.println("开始执行: " + method.getName());
try {
Object result = method.invoke(target, args);
return result;
} finally {
long end = System.currentTimeMillis();
System.out.println("执行完成: " + method.getName() + ", 耗时: " + (end - start) + "ms");
}
}
}
// CGLib方法拦截器
class LoggingMethodInterceptor implements MethodInterceptor {
private final Object target;
public LoggingMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("开始执行: " + method.getName());
try {
// 使用invokeSuper调用父类方法
Object result = proxy.invokeSuper(obj, args);
return result;
} finally {
long end = System.currentTimeMillis();
System.out.println("执行完成: " + method.getName() + ", 耗时: " + (end - start) + "ms");
}
}
}
七、总结
JDK动态代理和CGLib动态代理都是Java中强大的代理技术,各有其适用场景和优势:
- JDK动态代理基于接口实现,是Java标准库的一部分,使用简单但功能有限
- CGLib动态代理基于继承实现,功能更强大但需要第三方库支持
选择策略:
- 如果目标对象已经实现了接口,优先使用JDK动态代理
- 如果目标对象没有接口或者是需要更高性能的单例,使用CGLib动态代理
- 在Spring环境中,通常不需要手动选择,框架会自动选用合适的代理方式