简介
cglib是另外一种动态代理的方法,他和jdk动态代理的实现是有区别的,我们在之前见过jdk动态代理类是必须实现了接口的,而cglib不需要实现接口,但是必须保证类不含有final关键字,否则是无法代理的。 本文是从个人不小心遇到的cglib的死循环问题从而展开的分析。
cglib案例
下面我们来展示一个cglib的死循环案例。首先是要被代理的类,还是和常规的一样,声明自己的方法就行,但是要确保类和方法没有被final关键字修饰。用final关键字修饰类会直接报异常,但是修饰方法不会抛异常,但是此方法不会被代理,但是不影响其他方法被代理。
public class InfoDemo {
public void welcome (String person){
System.out.println("welcome :" + person);
}
}
下面是具体的代理类实现
public class CglibInfoProxy implements MethodInterceptor{
private Object target;
public Object newInstance(Object source){
target = source;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method!!!");
Object value = methodProxy.invoke(o, objects);
//Object value = methodProxy.invoke(this.target, objects);
//Object value = methodProxy.invokeSuper(o, objects);
return value;
}
public static void main(String[] args) {
//System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\\\classes");
InfoDemo instance = (InfoDemo) new CglibInfoProxy().newInstance(new InfoDemo());
instance.welcome("zhangsan");
}
}
和我们的jdk动态代理看起来十分相似,只是两个类实现的接口不同,并且生成对象的方法也不同。这里非常坑的是invoke
方法和invokeSuper
的区别,如果是用invoke方法一定要使用被代理的对象也就是上文中的target,而如果调用invokeSuper方法,则一定要使用被代理后的o对象。
上述这个例子就会引发死循环,导致StackOverflowFlow,嘿嘿,学没有,栈溢出的场景
具体为什么会这样,可以先思考一下,后面我们在源码实现中再去讲解。现在我们先看一下运行结果
...
before method!!!
before method!!!
before method!!!
Exception in thread "main" java.lang.StackOverflowError
at java.nio.CharBuffer.<init>(CharBuffer.java:281)
at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:70)
at java.nio.CharBuffer.wrap(CharBuffer.java:373)
at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
at java.io.PrintStream.write(PrintStream.java:526)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at com.eumji.proxy.cglib.CglibInfoProxy.intercept(CglibInfoProxy.java:30)
at com.eumji.proxy.cglib.InfoDemo?EnhancerByCGLIB?870a84d7.welcome(<generated>)
at com.eumji.proxy.cglib.InfoDemo?FastClassByCGLIB?2e560a7d.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
...
此处只展示部分的效果,具体可以自己试一下。
假如我们换成其余他两条语句将会是正确的输出,具体结果如下
before method!!!
welcome :zhangsan
原理解析
要想弄清楚的这到底是怎么回事,首先我们要看一下cglib代理后的类是怎样的,要想生成代理类的文件,我们只需要在我们的main方法中取消掉这句方法的注释
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");
就会在D盘的classes文件下生成对应的代理class文件,需要注意的是生成的代理class是有三个的,我们首先介绍一下我们最关心的InfoDemo代理类,其他的稍后合适的时机在描述其他两个。
InfoDemo反编译代码
其实就是将class文件直接拖到IDEA中
public class InfoDemo?EnhancerByCGLIB?870a84d7 extends InfoDemo implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$welcome$0$Method;
private static final MethodProxy CGLIB$welcome$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.eumji.proxy.cglib.InfoDemo?EnhancerByCGLIB?870a84d7");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = var10000[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
CGLIB$equals$2$Method = var10000[1];
CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Method = var10000[2];
CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Method = var10000[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Method = var10000[4];
CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.eumji.proxy.cglib.InfoDemo")).getDeclaredMethods())[0];
CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
}
final void CGLIB$welcome$0(String var1) {
super.welcome(var1);
}
public final void welcome(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
} else {
super.welcome(var1);
}
}
...
}
看代码就可以看出来cglib还是很复杂的,现在我们暂且可以看一下我们关心的welcome方法,从上面的代码中可以看到cglib是会为被代理类的方法同时生成两个代理方法的,一个是同名的welcome方法
和CGLIB$welcome$0方法
1.CGLIB$welcome$0方法
直接调用被代理的方法,也就是啥都没干。
2.welcome方法
首先判断有没有设置callback,很明显我们在代码中有设置即为CglibInfoProxy,所以就会调用CglibInfoProxy.intercept方法。
本来想分析一波生成代理类的过程,看了一下有点复杂,暂时就不分析了。。。。
invokeSuper方法
前面也提及了invoke和invokeSuper方法稍不注意就会出问题的问题,在这里我们从代码的层面去追踪一下,产生问题的原因。
我们看一下代理类方法invokeSuper的执行流程
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init(); //初始化fastInfo
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
invokeSuper在这里主要的作用就是初始化fastClassInfo。
init方法
private void init() {
if (this.fastClassInfo == null) {
Object var1 = this.initLock;
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
MethodProxy.CreateInfo ci = this.createInfo;
MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
上面的方法主要是加载methodProxy.FastClassInfo。ci是之前就初始化好的,其中c1指的就是被代理的类InfoDemo,c2则是com.eumji.proxy.cglib.InfoDemo?EnhancerByCGLIB?efe38465
这个代理类。
然后生成对应的f1和f2以及方法的下标i1和i2,i1和i2对应的就是在最前面所说的welcome方法
和CGLIB$welcome$0方法
,后面代码可以看出。
而f1则是对应InfoDemo?FastClassByCGLIB?2e560a7d
代理类,f2则对应InfoDemo?EnhancerByCGLIB?efe38465?FastClassByCGLIB?38345933
代理类。这些都可以在生成的代理class中去查看。
当然这里并没有说到底什么生成的,有兴趣的可以自己看一下字节码是怎么生成的。个人没太看懂。
invoke和invokeSuper区别
为什么要生成两个代理类f1和f2,我相信你看过之前的方法应该注意到了,在上面我们提及到了invoke方法和invokeSuper方法,我们来对比一下invoke方法和invokeSuper方法的区别
public Object invoke(Object obj, Object[] args) throws Throwable {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
}
我们可以看到invoke使用的是f1.invoke方法,而invokeSuper则是使用f2.invoke方法。
首先看一下f1对应的invoke方法逻辑
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
InfoDemo var10000 = (InfoDemo)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.welcome((String)var3[0]);
return null;
....
}
直接调用InfoDemo对象的welcome方法。
所以这也就能解释为什么我们之前会发生循环调用invoke的方法了,因为我们传入的var2是InfoDemo的代理对象,看最前面的代理类代码就可以看出,又会回到invoke方法,造成死循环。
再看一下f2中对应invoke的实现
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
efe38465 var10000 = (efe38465)var2;
int var10001 = var1;
try {
switch(var10001) {
....
var10000.CGLIB$finalize$1();
return null;
case 16:
var10000.CGLIB$welcome$0((String)var3[0]);
return null;
....
}
因为我们此时我们传入的var2是InfoDemo代理对象,所以最终会调用代理类中的CGLIB$welcome$0
方法。
小结
只是一次失败的源码分析尝试,不过弄清楚了造成调用死循环的原因,只能说cglib比jdk的动态代理复杂很多,主要体现在生成代码的逻辑和生成的代码上,还有待深入的学习。
而且是有两种invoke方法即invoke和invokeSuper方法,所以使用的时候必须要谨慎。
结语
本文出自个人笔记,如有表述不当或者纰漏的地方欢迎指正。
与君共勉!!!
简介
cglib是另外一种动态代理的方法,他和jdk动态代理的实现是有区别的,我们在之前见过jdk动态代理类是必须实现了接口的,而cglib不需要实现接口,但是必须保证类不含有final关键字,否则是无法代理的。
cglib案例
下面我们来展示一个cglib的死循环案例。首先是要被代理的类,还是和常规的一样,声明自己的方法就行,但是要确保类和方法没有被final关键字修饰。用final关键字修饰类会直接报异常,但是修饰方法不会抛异常,但是此方法不会被代理,但是不影响其他方法被代理。
public class InfoDemo {
public void welcome (String person){
System.out.println("welcome :" + person);
}
}
下面是具体的代理类实现
public class CglibInfoProxy implements MethodInterceptor{
private Object target;
public Object newInstance(Object source){
target = source;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method!!!");
Object value = methodProxy.invoke(o, objects);
//Object value = methodProxy.invoke(this.target, objects);
//Object value = methodProxy.invokeSuper(o, objects);
return value;
}
public static void main(String[] args) {
//System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\\\classes");
InfoDemo instance = (InfoDemo) new CglibInfoProxy().newInstance(new InfoDemo());
instance.welcome("zhangsan");
}
}
和我们的jdk动态代理看起来十分相似,只是两个类实现的接口不同,并且生成对象的方法也不同。这里非常坑的是invoke
方法和invokeSuper
的区别,如果是用invoke方法一定要使用被代理的对象也就是上文中的target,而如果调用invokeSuper方法,则一定要使用被代理后的o对象。
上述这个例子就会引发死循环,导致StackOverflowFlow,嘿嘿,学没有,栈溢出的场景
具体为什么会这样,可以先思考一下,后面我们在源码实现中再去讲解。现在我们先看一下运行结果
...
before method!!!
before method!!!
before method!!!
Exception in thread "main" java.lang.StackOverflowError
at java.nio.CharBuffer.<init>(CharBuffer.java:281)
at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:70)
at java.nio.CharBuffer.wrap(CharBuffer.java:373)
at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
at java.io.PrintStream.write(PrintStream.java:526)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at com.eumji.proxy.cglib.CglibInfoProxy.intercept(CglibInfoProxy.java:30)
at com.eumji.proxy.cglib.InfoDemo?EnhancerByCGLIB?870a84d7.welcome(<generated>)
at com.eumji.proxy.cglib.InfoDemo?FastClassByCGLIB?2e560a7d.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
...
此处只展示部分的效果,具体可以自己试一下。
假如我们换成其余他两条语句将会是正确的输出,具体结果如下
before method!!!
welcome :zhangsan
原理解析
要想弄清楚的这到底是怎么回事,首先我们要看一下cglib代理后的类是怎样的,要想生成代理类的文件,我们只需要在我们的main方法中取消掉这句方法的注释
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");
就会在D盘的classes文件下生成对应的代理class文件,需要注意的是生成的代理class是有三个的,我们首先介绍一下我们最关心的InfoDemo代理类,其他的稍后合适的时机在描述其他两个。
InfoDemo反编译代码
其实就是将class文件直接拖到IDEA中
public class InfoDemo?EnhancerByCGLIB?870a84d7 extends InfoDemo implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$welcome$0$Method;
private static final MethodProxy CGLIB$welcome$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.eumji.proxy.cglib.InfoDemo?EnhancerByCGLIB?870a84d7");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = var10000[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
CGLIB$equals$2$Method = var10000[1];
CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Method = var10000[2];
CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Method = var10000[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Method = var10000[4];
CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.eumji.proxy.cglib.InfoDemo")).getDeclaredMethods())[0];
CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
}
final void CGLIB$welcome$0(String var1) {
super.welcome(var1);
}
public final void welcome(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
} else {
super.welcome(var1);
}
}
...
}
看代码就可以看出来cglib还是很复杂的,现在我们暂且可以看一下我们关心的welcome方法,从上面的代码中可以看到cglib是会为被代理类的方法同时生成两个代理方法的,一个是同名的welcome方法
和CGLIB$welcome$0方法
1.CGLIB$welcome$0方法
直接调用被代理的方法,也就是啥都没干。
2.welcome方法
首先判断有没有设置callback,很明显我们在代码中有设置即为CglibInfoProxy,所以就会调用CglibInfoProxy.intercept方法。
本来想分析一波生成代理类的过程,看了一下有点复杂,暂时就不分析了。。。。
invokeSuper方法
前面也提及了invoke和invokeSuper方法稍不注意就会出问题的问题,在这里我们从代码的层面去追踪一下,产生问题的原因。
我们看一下代理类方法invokeSuper的执行流程
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init(); //初始化fastInfo
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
invokeSuper在这里主要的作用就是初始化fastClassInfo,然后通过fastClassInfo去调用目标方法。
init方法
此方法也有比较关键的地方,因为生成了代理类f1和f2。
private void init() {
if (this.fastClassInfo == null) {
Object var1 = this.initLock;
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
MethodProxy.CreateInfo ci = this.createInfo;
MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
fci.f1 = helper(ci, ci.c1); //fastclass代理类生成
fci.f2 = helper(ci, ci.c2);//fastclass代理类生成
fci.i1 = fci.f1.getIndex(this.sig1); //获取下标
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
上面的方法主要是加载methodProxy.FastClassInfo。ci是之前就初始化好的,其中c1指的就是被代理的类InfoDemo,c2则是com.eumji.proxy.cglib.InfoDemo?EnhancerByCGLIB?efe38465
这个代理类。
然后生成对应的f1和f2以及方法的下标i1和i2,i1和i2对应的就是在最前面所说的welcome方法
和CGLIB$welcome$0方法
,后面代码可以看出。
而f1则是对应InfoDemo?FastClassByCGLIB?2e560a7d
代理类,f2则对应InfoDemo?EnhancerByCGLIB?efe38465?FastClassByCGLIB?38345933
代理类。这些都可以在生成的代理class中去查看。
当然这里并没有说到底什么生成的,有兴趣的可以自己看一下字节码是怎么生成的。个人没太看懂。
invoke和invokeSuper区别
为什么要生成两个代理类f1和f2,我相信你看过之前的方法应该注意到了,在上面我们提及到了invoke方法和invokeSuper方法,我们来对比一下invoke方法和invokeSuper方法的区别
public Object invoke(Object obj, Object[] args) throws Throwable {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
}
我们可以看到invoke使用的是f1.invoke方法,而invokeSuper则是使用f2.invoke方法。
首先看一下f1对应的invoke方法逻辑
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
InfoDemo var10000 = (InfoDemo)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.welcome((String)var3[0]);
return null;
....
}
直接调用InfoDemo对象的welcome方法。
所以这也就能解释为什么我们之前会发生循环调用invoke的方法了,因为我们传入的var2是InfoDemo的代理对象,看最前面的代理类代码就可以看出,又会回到invoke方法,造成死循环。
再看一下f2中对应invoke的实现
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
efe38465 var10000 = (efe38465)var2;
int var10001 = var1;
try {
switch(var10001) {
....
var10000.CGLIB$finalize$1();
return null;
case 16:
var10000.CGLIB$welcome$0((String)var3[0]);
return null;
....
}
因为我们此时我们传入的var2是InfoDemo代理对象,所以最终会调用代理类中的CGLIB$welcome$0
方法。
小结
只是一次失败的源码分析尝试,不过弄清楚了造成调用死循环的原因,只能说cglib比jdk的动态代理复杂很多,主要体现在生成代码的逻辑和生成的代码上,还有待深入的学习。
而且是有两种invoke方法即invoke和invokeSuper方法,所以使用的时候必须要谨慎。
结语
本文出自个人笔记,如有表述不当或者纰漏的地方欢迎指正。
与君共勉!!!