学习动态代理前,建议先学习下Java 反射与实践

在Java中,动态代理是一种设计模式,它允许在运行时创建代理类和代理对象,从而在不修改目标对象代码的情况下,为对象提供额外的功能,比如日志记录、事务管理、安全检查等。

Java 中动态代理的实现有2种方式

  1. JDK 动态代理
  2. CGLIB 动态代理

下面将从实际代码出发,对其实现原理进行解释。

1.JDK 动态代理

JDK 动态代理是 Java 标准库中提供的一种代理实现机制

  1. 它主要依靠 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来创建和管理代理对象。
  2. JDK 动态代理只能通过实现接口的方式生成代理对象

1.1 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

public interface HelloWorld {
void sayHello();
void sayBye();
}

public class HelloWorldImpl implements HelloWorld {

@Override
public void sayHello() {
System.out.println("Hello, world!");
}

@Override
public void sayBye() {
System.out.println("Bye bye, world!");
}
}


public class HelloWorldHandler implements InvocationHandler {
private Object target;

public HelloWorldHandler(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;
}
}


public class JdkDynamicProxyDemo {

public static void main(String[] args) {
HelloWorld helloWorld = new HelloWorldImpl();
HelloWorldHandler handler = new HelloWorldHandler(helloWorld);

HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
helloWorld.getClass().getClassLoader(),
helloWorld.getClass().getInterfaces(),
handler);

proxy.sayHello();
proxy.sayBye();
}
}

运行结果如下

接下来进行源码分析

1.2 创建动态代理对象-Proxy.newProxyInstance

1
2
3
4
HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
helloWorld.getClass().getClassLoader(),
helloWorld.getClass().getInterfaces(),
handler);

Proxy 通过类加载器和接口集合动态地生成或查找代理类,并使用提供的InvocationHandler创建一个新的代理实例, 进入源码看一下

1
2
3
4
5
6
7
8
9
10
11
12
public class Proxy implements java.io.Serializable {

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {

Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

return newProxyInstance(caller, cons, h);
}
}

我们将重点分析newProxyInstance 这个方法, 进入源码, 该方法主要流程可以分为2个步骤,都是使用反射的典型步骤

  1. getProxyConstructor
  2. newProxyInstance

    1.3 getProxyConstructor

getProxyConstructor是一个私有方法,负责获取或生成代理类的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Proxy implements java.io.Serializable {
private static Constructor<?> getProxyConstructor(Class<?> caller,ClassLoader loader,Class<?>... interfaces)
{
// optimization for single interface
// 单接口优化,大多数实际应用中,代理通常只需要实现一个接口,这种情况提供了优化的机会。单接口优化的主要目的是减少处理时间和简化生成代理类的复杂度。
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// interfaces cloned
// 当有多个接口时,方法首先克隆接口数组,以避免修改原始数据。这是一种防御性编程技巧,确保数据的不可变性。
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}
}
  1. proxyCache.sub(intf).computeIfAbsent: 这是一种高效的缓存访问模式,computeIfAbsent方法来尝试获取或创建代理类的构造函数, 如果缓存中没有相应的构造函数,会调用ProxyBuilder来动态生成一个新的代理类,并将其存储在缓存中。
  2. checkProxyAccess: 这个方法检查调用者是否有权限使用指定的类加载器创建代理。这是一个安全检查,防止安全漏洞。

直接进入build 看创建流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Constructor<?> build() {
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
try {
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
return cons;
}

1.3.1 defineProxyClass -获取代理类的Class对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

private static final class ProxyBuilder {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();

// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";

// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();

// a reverse cache of defined proxy classes
private static final ClassLoaderValue<Boolean> reverseProxyCache =
new ClassLoaderValue<>();

// interfaces需要代理的接口列表
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
// 代理类的包名,初始为 null。
String proxyPkg = null;
// 代理类的访问标志,初始为 PUBLIC | FINAL。
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 遍历接口列表
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
// 如果接口不是public,则认为是包级私有的。将 accessFlags 设置为 FINAL,并记录包名。
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL; // non-public, final
String pkg = intf.getPackageName();
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
// 如果所有非public接口不在同一个包中,则抛出异常。
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 确定代理类的包名
if (proxyPkg == null) {
// all proxy interfaces are public
// PROXY_PACKAGE_PREFIX 是 “com.sun.proxy”
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
: PROXY_PACKAGE_PREFIX;
} else if (proxyPkg.isEmpty() && m.isNamed()) {
throw new IllegalArgumentException(
"Unnamed package cannot be added to " + m);
}

if (m.isNamed()) {
if (!m.getDescriptor().packages().contains(proxyPkg)) {
throw new InternalError(proxyPkg + " not exist in " + m.getName());
}
}

/*
* Choose a name for the proxy class to generate.
*/
// 使用一个全局计数器生成唯一的代理类名称。
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg.isEmpty()
? proxyClassNamePrefix + num
: proxyPkg + "." + proxyClassNamePrefix + num;
// 获取用于加载代理类的类加载器。
ClassLoader loader = getLoader(m);
trace(proxyName, m, loader, interfaces);

/*
* Generate the specified proxy class.
*/
// 使用 ProxyGenerator 生成代理类的字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
// 将字节码转换为 Class 对象并加载到 JVM 中
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}

1.3.1.1 代理类包名-proxyPkg

关于 proxyPkg 的逻辑, 从上面的代码中可以看出:

  1. 如果有包级私有接口:代理类必须定义在这些接口所在的包中,以保证访问权限的一致性。如果包级私有接口分布在不同的包中,会抛出异常。
  2. 如果没有包级私有接口:如果所有接口都是公共的(public),代理类会默认定义在 com.sun.proxy 包中,或者 com.sun.proxy.<module_name>(如果模块被命名)。

这种设计确保了代理类生成时能够正确处理访问权限,同时提供了一个默认的命名空间(com.sun.proxy)来存放公共接口的代理类。

接口权限级别

在Java中,类和接口的权限级别主要有以下四种(但接口只使用其中两种 public 和 Package-private ):

  1. Public(公共):任何地方都可以访问。
  2. Protected(受保护的):不能用于顶级类或接口,仅用于类的成员,允许同包中的类和子类访问。
  3. Package-private(包级私有):如果没有指定访问修饰符,表示只有同一个包中的类可以访问。
  4. Private(私有的):不能用于顶级类或接口,仅用于类的成员,表示只有该类本身可以访问。

代理类必须能够访问所有需要代理的接口,才能进行实现。

生成代理类时,如果遇到多个非public接口(包级私有接口)位于不同的包中,代理类将无法同时访问这些接口。这是因为包级私有接口只能在定义它们的包内访问。因此,必须确保所有包级私有接口在同一个包中,否则会抛出异常。

同时如果代理类需要实现的接口中,有包级私有(package-private)接口,那么代理类应该被标记为final,不能再被其他类继承。从而确保这些包级私有接口的实现不会被其他包中的类继承和使用,从而遵循严格的访问控制规则。

Modifier.PUBLIC | Modifier.FINAL

这段代码表示一个组合的访问修饰符,其中 | 是按位或运算符,将两个修饰符组合在一起。具体来说:

  • Modifier.PUBLIC:表示公共访问修饰符,值为 1。公共修饰符表示该类或成员可以被任何其他类访问。
  • Modifier.FINAL:表示最终修饰符,值为 16。最终修饰符表示该类不能被继承,或该方法不能被重写,或该字段的值不能被更改。

在定义代理类时,通过按位或运算符 |,组合的修饰符 Modifier.PUBLIC | Modifier.FINAL 可以用于确保生成的代理类是公共且不可继承的。这在动态代理生成过程中非常重要,确保代理类的正确访问级别和不可变性。

1.3.1.2 代理类全限定名-proxyName

proxyName 指的是代理类的全限定名,全限定名包括包名和类名,这样可以确保代理类在类加载器范围内的唯一性,并且符合 Java 类命名的规范。。

根据以上代码,可以看出其生成逻辑

  1. 确定proxyPkg
  2. 全局计数器,nextUniqueNumber 是一个全局的 AtomicLong,用于生成唯一的数字。getAndIncrement 方法保证每次调用都会返回一个唯一的数字,并将计数器递增。

proxyPkg不为空的情况下,proxyName 长这样, com.sun.proxy.Proxy0com.sun.proxy.module.Proxy0, 其中0可以替换成递增的数字

1.3.1.3 代理类字节码

1
byte[] ProxyGenerator.generateProxyClass(String proxyName, Class<?>[] interfaces, int accessFlags)

proxyName:代理类的全限定名,如 com.sun.proxy.Proxy0

interfaces:代理类需要实现的接口数组。

accessFlags:代理类的访问修饰符, 如 `Modifier.PUBLIC | Modifier.FINAL

generateProxyClass 方法通过字节码生成技术,创建一个新的代理类的字节码

1.3.2 getConstructor

获取代理类构造函数,且是获取参数为InvocationHandler 的构造函数

1.4 newProxyInstance

在反射中技术中,经典的获取实例对象的方法就是

1
cons.newInstance

在前面的步骤中,获取了参数为InvocationHandler 的构造函数 Constructor, 那么在实例化代理对象时, 就需要传入类型为InvocationHandler的参数, 本文示例代码中传入的是实现了InvocationHandler 的 HelloWorldHandler

1.5 基于动态对象执行目标方法

1
proxy.sayHello();

调用代理对象的方法, 会进入自定义的InvocationHandler.invoke方法
在invoke 方法中,

  1. 可以添加自定义逻辑,比如在AOP 中,可以添加增强
  2. 执行目标类的真正方法

至此代理对象已经生成。

1.6 反射在JDK 动态代理中的应用

根据以上源码和示例代理的分析,可以看到 JDK 动态代理完全基于反射运行。主要体现在以下几个方面:

  1. 接口方法的动态实现
    • 通过使用Proxy.newProxyInstance生成的代理类,实际上在内存中创建了一个实现了指定接口的新类。这个过程是完全动态的,利用反射机制查询接口方法并在代理类中生成相应的方法实现。
  2. 方法调用的拦截
    • InvocationHandlerinvoke方法的实现通常会使用反射来动态调用目标对象的方法。通过Method对象的invoke方法,可以在运行时调用任何对象的任意方法。
  3. 类型信息的动态处理
    • 反射机制允许动态代理在运行时查询关于类和接口的元数据(如方法名、参数类型、返回类型等),这些信息对于正确转发方法调用是必需的。

2. CGLIB动态代理

CGLIB通过生成目标类的子类来实现代理

  1. 可以代理没有实现接口的类和实现了接口的类, 相比JDK 只能代理实现了接口的类,有较强的灵活性
  2. 但不能代理final类和final方法,并且有性能开销和依赖CGLIB库的缺点。

2.1 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public interface HelloWorld {  

void sayHello();
void sayBye();

}

public class HelloWorldImpl implements HelloWorld {
@Override
public void sayHello() {
System.out.println("Hello, world!");
}

@Override
public void sayBye() {
System.out.println("Bye bye, world!");
}
}

public class HelloWorldInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method: " + method.getName());
return result;
}
}


public class CglibDynamicProxyDemo {

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloWorldImpl.class);
enhancer.setCallback(new HelloWorldInterceptor());

HelloWorld proxy = (HelloWorld) enhancer.create();
proxy.sayHello();
proxy.sayBye();
}
}

运行结果如下

2.2 Enhancer

Enhancer 类用于生成一个目标类的子类(即代理类),并通过在代理类中拦截方法调用来实现动态代理。这个过程包括指定要代理的目标类、设置回调、生成代理类等步骤。

1
public class Enhancer extends AbstractClassGenerator {}
  1. setSuperclass(Class<?> superclass)
    设置代理类的父类,即目标类。代理类将继承这个父类,从而可以拦截父类的方法调用。

  2. setCallback(Callback callback)
    设置回调,用于拦截方法调用并定义拦截逻辑。Callback 是一个接口,MethodInterceptor 是其常用的实现之一。

  3. create()
    生成代理类的实例。create 方法使用先前设置的父类和回调来动态创建代理类。

2.3 MethodInterceptor

1
2
3
4
5
6
7
8
9
10
11
enhancer.setCallback(new HelloWorldInterceptor());  

public class HelloWorldInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method: " + method.getName());
return result;
}
}

自定义的HelloWorldInterceptor实现了MethodInterceptor

MethodInterceptorCallback 接口的一个实现,其核心方法是 intercept,用于定义方法调用时的拦截逻辑。

每次通过代理类调用目标方法时,都会经过intercept 方法去调用父类中真正的方法, 其作用类似于InvocationHandler.invoke

1
2
3
4
5
6
7
8
package org.springframework.cglib.proxy;    
public interface Callback {
}

package org.springframework.cglib.proxy;
public interface MethodInterceptor extends Callback {
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
  • obj:代理对象本身。
  • Method method:被拦截的方法对象, 就是反射中的那个Method
  • args:方法参数
  • MethodProxy proxy:用于调用父类方法的代理对象。

当执行代理对象的方法时, 其逻辑会进入到intercept 方法中

2.4 MethodProxy

在代理类的生成过程中,CGLIB 会为每个方法创建对应的 MethodProxy 对象。MethodProxy 对象封装了目标方法的信息,并且在调用时可以直接调用父类的方法,而不需要通过反射机制。

与直接使用反射调用方法相比,MethodProxy 具有更高的性能。因为 MethodProxy 通过生成的字节码直接调用目标类的方法,避免了反射调用的开销。反射调用方法时,需要进行一系列检查和操作,而 MethodProxy 可以在生成字节码时优化这些操作,从而提高方法调用的效率。

2.4.1 create

结合debug 信息, 解释MethodProxy.create 方法中,各个参数的含义

  • c1:代理类的父类,即目标类
  • c2::代理类
  • desc:用于描述方法的签名。在图中显示为 "()V",表示方法没有参数,返回值为 void
  • name1:目标类中需要代理的方法名
  • name2:代理类中对应的代理方法名。

2.4.2 invokeSuper

1
2
3
4
5
6
7
8
9
10
11
12
public Object invokeSuper(Object obj, Object[] args) throws Throwable {  
try {
// 初始化 MethodProxy 对象,确保 FastClass 已初始化
init();
FastClassInfo fci = fastClassInfo;
// 通过 FastClass 调用父类方法
return fci.f2.invoke(fci.i2, obj, args);
}
catch (InvocationTargetException e) {
throw e.getTargetException();
}
}

2.5 创建动态代理对象-enhancer.create

enhancer实例设置了必要信息后,就可以调用create 方法创建代理对象了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Enhancer extends AbstractClassGenerator {
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}

private Object createHelper() {
preValidate();
// 创建了一个唯一的键 `key`,用于标识生成的代理类
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}

}

2.5.1 AbstractClassGenerator.create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
abstract public class AbstractClassGenerator<T> implements ClassGenerator {
protected Object create(Object key) {
try {
// 获取用于加载代理类的类加载器
// 这个类加载器通常是目标类的类加载器。
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
//从缓存中获取与当前类加载器对应的 `ClassLoaderData` 对象。
ClassLoaderData data = cache.get(loader);
if (data == null) {
// 双重检查锁定
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
// 创建新数据,使用 `WeakHashMap` 作为缓存,允许类加载器被垃圾回收。
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
data = new ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
// 从 `ClassLoaderData` 对象中获取或生成代理类。
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
}
catch (RuntimeException | Error ex) {
throw ex;
}
catch (Exception ex) {
throw new CodeGenerationException(ex);
}
}
}

2.6 基于动态对象执行目标方法

1
proxy.sayHello();

执行业务代码, 会进入自定义的HelloWorldInterceptor.intercept 方法
在 intercept 方法中,

  1. 可以添加自定义逻辑,比如在AOP 中,可以添加增强
  2. 执行目标类的真正方法

2.7 代理类命名规则

在CGLIB生成的代理类名称中,$$$$通常用来分隔原始类名和附加的代理类信息。这种命名约定使得在调试、日志记录以及运行时反射操作中,更容易识别和处理代理类。

1
HelloWorldImpl$$EnhancerByCGLIB$$abcdef12 

  • HelloWorldImpl 是原始类的名称。
  • $$$$ 是CGLIB的类分隔符。
  • EnhancerByCGLIB 表示这是一个由CGLIB生成的增强类。
  • abcdef12 是一串随机字符或哈希值,用于区分不同的代理实例。

判断类名是否包含$$$$。这可以识别出当前类是否是CGLIB生成的代理类。

2.8 ASM 与 CGLIB 的关系

CGLIB 是一个基于 ASM 的字节码操作库。ASM 是一个底层的字节码操作框架,提供了更细粒度的字节码操作功能。CGLIB 在此基础上进行了更高层次的封装,简化了字节码操作的复杂性,使得开发人员可以更方便地生成和操作字节码。

2.8.1 ASM 的特点

  • 低级别字节码操作:ASM 允许开发人员直接操作 Java 字节码,提供了极大的灵活性和控制力。
  • 高性能:由于直接操作字节码,ASM 的性能非常高,可以用于生成高效的字节码。

    2.8.2 CGLIB 的特点

  • 基于 ASM 封装:CGLIB 在 ASM 的基础上进行了封装,提供了更高层次的 API,使得字节码操作更为简便。
  • 简化动态代理实现:通过 CGLIB,开发人员可以更方便地实现动态代理,而无需深入理解字节码的具体细节。

3 JDK vs CGLib

3.1 实现机制

JDK动态代理

  • 基于接口:JDK 动态代理要求目标类实现一个或多个接口。代理类实现这些接口,并将方法调用委托给 InvocationHandler
  • 反射机制:JDK 动态代理使用反射机制来调用目标方法。通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。
  • 字节码生成:JDK 动态代理使用 JVM 自带的字节码生成工具生成代理类的字节码,不依赖外部库。

CGLIB动态代理

  • 基于继承:CGLIB 动态代理通过继承目标类来创建代理类。因此,它不要求目标类实现接口。
  • 字节码生成:CGLIB 使用 ASM 库在运行时生成代理类的字节码。代理类继承目标类,并通过方法拦截器(MethodInterceptor)拦截方法调用。
  • FastClass 机制:CGLIB 提供 FastClass 机制,通过索引直接调用方法,避免了反射调用的开销,提高了性能。

3.2 适用场景

JDK动态代理

  • 适用于实现了接口的类:JDK 动态代理只能代理实现了一个或多个接口的类。
  • 简单场景:适用于需要代理的类和方法比较简单,且符合接口约束的场景。

CGLIB动态代理

  • 适用于未实现接口的类:CGLIB 可以代理未实现接口的类,适用范围更广。
  • 复杂场景:适用于需要代理的类和方法较复杂,或者无法修改目标类以实现接口的场景。

3.3 性能比较

JDK动态代理

  • 反射开销:由于使用反射机制,方法调用的性能可能较低,特别是在大量调用时,反射开销显著。
  • 方法调用:每次方法调用都需要通过 InvocationHandler,导致一定的性能损失。

CGLIB动态代理

  • 字节码生成:通过 ASM 库动态生成字节码,性能较高。
  • FastClass 机制:通过 FastClass 机制,方法调用的性能接近于直接调用,远高于反射调用。

3.4 内存开销

JDK动态代理

  • 较低内存开销:由于代理类较为简单,且不需要生成额外的字节码,内存开销较低。
  • 类加载器:代理类由 JVM 生成并加载,不依赖外部类加载器。

CGLIB动态代理

  • 较高内存开销:需要生成和加载额外的字节码,内存开销较高。
  • 类加载器:生成的代理类需要额外的类加载器进行加载和管理。

3.5 可维护性和扩展性

JDK动态代理

  • 易于理解和维护:基于接口的代理模式较为简单,易于理解和维护。
  • 扩展性有限:只能代理实现了接口的类,对于未实现接口的类,需要修改类结构。

CGLIB动态代理

  • 复杂度较高:由于涉及字节码生成和方法拦截,理解和维护较为复杂。
  • 高扩展性:可以代理未实现接口的类,适用范围更广,更具扩展性。