学习动态代理前,建议先学习下Java 反射与实践
在Java中,动态代理是一种设计模式,它允许在运行时创建代理类和代理对象,从而在不修改目标对象代码的情况下,为对象提供额外的功能,比如日志记录、事务管理、安全检查等。
Java 中动态代理的实现有2种方式
JDK 动态代理
CGLIB 动态代理
下面将从实际代码出发,对其实现原理进行解释。
1.JDK 动态代理 JDK 动态代理是 Java 标准库中提供的一种代理实现机制
它主要依靠 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来创建和管理代理对象。
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个步骤,都是使用反射的典型步骤
getProxyConstructor
newProxyInstance1.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) { 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 { 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() ); } } }
proxyCache.sub(intf).computeIfAbsent : 这是一种高效的缓存访问模式,computeIfAbsent
方法来尝试获取或创建代理类的构造函数, 如果缓存中没有相应的构造函数,会调用ProxyBuilder
来动态生成一个新的代理类,并将其存储在缓存中。
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(); private static final String proxyClassNamePrefix = "$Proxy" ; private static final AtomicLong nextUniqueNumber = new AtomicLong (); private static final ClassLoaderValue<Boolean> reverseProxyCache = new ClassLoaderValue <>(); private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) { String proxyPkg = null ; int accessFlags = Modifier.PUBLIC | Modifier.FINAL; for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String pkg = intf.getPackageName(); if (proxyPkg == null ) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException ( "non-public interfaces from different packages" ); } } } if (proxyPkg == null ) { 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()); } } long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg.isEmpty() ? proxyClassNamePrefix + num : proxyPkg + "." + proxyClassNamePrefix + num; ClassLoader loader = getLoader(m); trace(proxyName, m, loader, interfaces); byte [] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags); try { 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
的逻辑, 从上面的代码中可以看出:
如果有包级私有接口 :代理类必须定义在这些接口所在的包中,以保证访问权限的一致性。如果包级私有接口分布在不同的包中,会抛出异常。
如果没有包级私有接口 :如果所有接口都是公共的(public
),代理类会默认定义在 com.sun.proxy
包中,或者 com.sun.proxy.<module_name>
(如果模块被命名)。
这种设计确保了代理类生成时能够正确处理访问权限,同时提供了一个默认的命名空间(com.sun.proxy
)来存放公共接口的代理类。
接口权限级别 在Java中,类和接口的权限级别主要有以下四种(但接口只使用其中两种 public 和 Package-private ):
Public(公共) :任何地方都可以访问。
Protected(受保护的) :不能用于顶级类或接口,仅用于类的成员,允许同包中的类和子类访问。
Package-private(包级私有) :如果没有指定访问修饰符,表示只有同一个包中的类可以访问。
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 类命名的规范。。
根据以上代码,可以看出其生成逻辑
确定proxyPkg
全局计数器,nextUniqueNumber
是一个全局的 AtomicLong
,用于生成唯一的数字。getAndIncrement
方法保证每次调用都会返回一个唯一的数字,并将计数器递增。
proxyPkg不为空的情况下,proxyName 长这样, com.sun.proxy.Proxy0
或 com.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 在反射中技术中,经典的获取实例对象的方法就是 在前面的步骤中,获取了参数为InvocationHandler
的构造函数 Constructor, 那么在实例化代理对象时, 就需要传入类型为InvocationHandler
的参数, 本文示例代码中传入的是实现了InvocationHandler
的 HelloWorldHandler
1.5 基于动态对象执行目标方法
调用代理对象的方法, 会进入自定义的InvocationHandler.invoke
方法 在invoke 方法中,
可以添加自定义逻辑,比如在AOP 中,可以添加增强
执行目标类的真正方法
至此代理对象已经生成。
1.6 反射在JDK 动态代理中的应用 根据以上源码和示例代理的分析,可以看到 JDK 动态代理完全基于反射运行。主要体现在以下几个方面:
接口方法的动态实现 :
通过使用Proxy.newProxyInstance
生成的代理类,实际上在内存中创建了一个实现了指定接口的新类。这个过程是完全动态的,利用反射机制查询接口方法并在代理类中生成相应的方法实现。
方法调用的拦截 :
InvocationHandler
的invoke
方法的实现通常会使用反射来动态调用目标对象的方法。通过Method
对象的invoke
方法,可以在运行时调用任何对象的任意方法。
类型信息的动态处理 :
反射机制允许动态代理在运行时查询关于类和接口的元数据(如方法名、参数类型、返回类型等),这些信息对于正确转发方法调用是必需的。
2. CGLIB动态代理 CGLIB通过生成目标类的子类来实现代理
可以代理没有实现接口的类和实现了接口的类, 相比JDK 只能代理实现了接口的类,有较强的灵活性
但不能代理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 {}
setSuperclass(Class<?> superclass)
设置代理类的父类,即目标类。代理类将继承这个父类,从而可以拦截父类的方法调用。
setCallback(Callback callback)
设置回调,用于拦截方法调用并定义拦截逻辑。Callback
是一个接口,MethodInterceptor
是其常用的实现之一。
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
MethodInterceptor
是 Callback
接口的一个实现,其核心方法是 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 { init(); FastClassInfo fci = fastClassInfo; 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(); 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 data = cache.get(loader); if (data == null ) { synchronized (AbstractClassGenerator.class) { cache = CACHE; data = cache.get(loader); if (data == null ) { Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap <ClassLoader, ClassLoaderData>(cache); data = new ClassLoaderData (loader); newCache.put(loader, data); CACHE = newCache; } } } this .key = key; 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 基于动态对象执行目标方法
执行业务代码, 会进入自定义的HelloWorldInterceptor.intercept
方法 在 intercept 方法中,
可以添加自定义逻辑,比如在AOP 中,可以添加增强
执行目标类的真正方法
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动态代理
复杂度较高 :由于涉及字节码生成和方法拦截,理解和维护较为复杂。
高扩展性 :可以代理未实现接口的类,适用范围更广,更具扩展性。