Spring学习(五):一篇讲清楚动态代理(jdk和cglib)的使用、原理和源码

news/2024/5/10 19:49:19/文章来源:https://blog.csdn.net/m0_49499183/article/details/130069199

目录

一、jdk动态代理的基本使用

二、cglib动态代理的基本使用

2.1 方法一:method.invoke() 方法反射调用

2.2 方法二(spring使用的这个方法): methodProxy.invoke()

2.3 方法三:methodProxy.invokeSuper()

三、jdk实现代理的原理 

四、jdk实现代理的源码

五、jdk对代理的优化 

六、cglib实现动态代理的原理

七、cglib动态代理如何避免反射——methodProxy原理

7.1  methodProxy的使用

7.2 methodProxy不使用反射的原理

7.3 与jdk反射优化的性能对比


一、jdk动态代理的基本使用

        在下面的模拟中,我们的代理目标是Target类,他实现了Foo接口。在main方法中,我们模拟jdk实现动态代理的方法,来模拟实现AOP代理增强。 

        需要注意的一点是:jdk只能针对接口代理。

public class JdkProxyDemo {interface Foo {void foo();}/*** 目标类,实现了Foo接口。 jdk只能针对接口代理*/static final class Target implements Foo {public void foo() {System.out.println("target foo");}}public static void main(String[] param) throws IOException {// 目标对象Target target = new Target();// 用来加载在运行期间动态生成的字节码。 因为代理类并没有.java的源代码,他是在运行期直接生成的字节码ClassLoader loader = JdkProxyDemo.class.getClassLoader();/*** newProxyInstance生成一个代理实例。*   参数:1、类加载器;2、要实现的接口;3、InvocationHandler,用于规定所实现接口的增强方法的行为*///因为Proxy实例实现了Foo接口,所以可以强转为Foo类型Foo proxy = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (p, method, args) -> {// 模拟前置增强System.out.println("before...");// 反射调用,让target执行method方法。 普通调用:目标.方法(参数);    反射调用:方法.invoke(目标, 参数);Object result = method.invoke(target, args);// 模拟后置增强System.out.println("after....");// 让代理也返回目标方法执行的结果return result;});proxy.foo();}
}

        运行结果: 

before...
target foo
after....

        需要注意:代理对象Proxy 和 代理目标Target是兄弟关系,他们都实现了Foo接口。所以,目标类Target也可以是final类型,这点与cglib实现的动态代理不同。 

二、cglib动态代理的基本使用

        与jdk的实现不同,cglib的实现是基于父子类的,代理类作为目标类的子类,因此目标类和目标方法不能是final类型。

        除了用method反射调用外,cglib还提供了methodProxy参数来避免反射调用,从而提升效率(原理在本文第七章中有讲解)。因此,cglib主要有三种实现方式。

        在下面的实例中,目标类为Target。

 2.1 方法一:method.invoke() 方法反射调用

public class CglibProxyDemo {static class Target {public void foo() {System.out.println("target foo");}}// 代理是子类型, 目标是父类型public static void main(String[] param) {// 目标对象Target target = new Target();/***  创建一个代理类*  参数:1、目标类本身; 2、回调函数,决定代理类中方法执行的行为*      回调函数的参数:1、代理类对象本身; 2、当前被代理类中执行的方法; 3、方法执行的实际参数*/// 子类型的对象可以转换为父类型,所以Proxy可以转换为TargetTarget proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {//模拟前置增强System.out.println("before...");// 方法一:用方法反射调用目标Object result = method.invoke(target, args); //模拟后置增强System.out.println("after...");return result;});proxy.foo();}
}

2.2 方法二(spring使用的这个方法): methodProxy.invoke()

public class CglibProxyDemo {static class Target {public void foo() {System.out.println("target foo");}}// 代理是子类型, 目标是父类型public static void main(String[] param) {Target target = new Target();/***  创建一个代理类*  参数:1、目标类本身; 2、回调函数,决定代理类中方法执行的行为*      回调函数的参数:1、代理类对象本身; 2、当前被代理类中执行的方法; 3、方法执行的实际参数; 4、methodProxy可以避免反射调用目标*/// 子类型的对象可以转换为父类型,所以Proxy可以转换为TargetTarget proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {//模拟前置增强System.out.println("before...");// methodProxy 可以避免反射调用// 内部没有用反射, 需要目标(spring使用的是这种方法)Object result = methodProxy.invoke(target, args);  //模拟后置增强System.out.println("after...");return result;});proxy.foo();}
}

 2.3 方法三:methodProxy.invokeSuper()

public class CglibProxyDemo {static class Target {public void foo() {System.out.println("target foo");}}// 代理是子类型, 目标是父类型public static void main(String[] param) {/***  创建一个代理类*  参数:1、目标类本身; 2、回调函数,决定代理类中方法执行的行为*      回调函数的参数:1、代理类对象本身; 2、当前被代理类中执行的方法; 3、方法执行的实际参数; 4、methodProxy可以避免反射调用目标*/// 子类型的对象可以转换为父类型,所以Proxy可以转换为TargetTarget proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {//模拟前置增强System.out.println("before...");// 内部没有用反射, 需要代理类对象,不需要目标类对象Object result = methodProxy.invokeSuper(p, args); //模拟后置增强System.out.println("after...");return result;});proxy.foo();}
}

三、jdk实现代理的原理 

        jdk实现代理的源码在Proxy.newProxyInstance() 中,它是通过ASM动态地生成代理类的,我们看不到代理类对应的Java代码,想要阅读他的源码并不容易。因此,我们抛开jdk的源码,思考一下如果代理的原理,如果我们自己实现,应该如何去实现;然后再去阅读jdk源码,这样可能会有更多的收获。

        我这里准备好了一段代码:

public class A13 {interface Foo {void foo();}static class Target implements Foo {@Overridepublic void foo() {System.out.println("target foo");}}public static void main(String[] args) {}
}

        代码中有一个需要增强的目标类Target,他实现了Foo接口,下一步就要使用代理类来增强它了。在这里我不使用jdk的代理类,而是手写一个代理类。根据前面一中的介绍,jdk实现代理类需要和目标类实现共同的接口:

public class $Proxy0 implements Foo {@Overridepublic void foo() {// 1.功能增强System.out.println("before...");// 2.调用目标new Target().foo();}
}

        再在main方法中使用代理类调用方法:

public class A13 {interface Foo {void foo();}static class Target implements Foo {@Overridepublic void foo() {System.out.println("target foo");}}public static void main(String[] args) {Foo proxy = new $Proxy0();proxy.foo();}
}

         运行结果:

before...
target foo

        这样我们就实现了基本的代理增强。但是问题也随之而来:我们的代理类目前是把功能增强的逻辑和调用目标的逻辑写死在类中,但在实际的项目中, 我们并不会固定增强的功能,比如我们可能要做日志增强,也可能要做权限验证的增强;同样的,调用目标也不会固定,我们不一定会只调foo方法,也可能会调其他方法,甚至不一定会调方法(比如做权限认证,权限通过了才调方法,权限认证没通过,就可能抛出异常或者做其他处理了)。总之,这样不确定的代码我们不应该放在代理内部,而应该写成抽象方法,剥离到类外。

        于是我们建一个InvocationHandler接口,声明invoke方法,将功能增强和调用目标的逻辑写在其中:

public class A13 {interface Foo {void foo();}static class Target implements Foo {@Overridepublic void foo() {System.out.println("target foo");}}interface InvocationHandler {void invoke();}public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke() {// 1.功能增强System.out.println("before...");// 2.调用目标new Target().foo();}});proxy.foo();}
}
public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {h.invoke();}
}

        其实这样代理类也无需知道目标是谁了,只要实现父接口即可。 

        接下来的问题就是调用方法了,不一定每次调的都是foo方法,比如Foo接口当中加入了其他的方法。所以我们的代理类要能够知道调用目标类的哪个方法。这里就需要用到反射了,我们可以通过反射拿到当前调用的方法是哪一个。

public class A13 {interface Foo {void foo();void bar();}static class Target implements Foo {@Overridepublic void foo() {System.out.println("target foo");}@Overridepublic void bar() {System.out.println("target bar");}}interface InvocationHandler {void invoke(Method method, Object []args) throws Throwable;}public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1.功能增强System.out.println("before...");// 2.调用目标
//                new Target().foo();method.invoke(new Target(), args);}});proxy.foo();proxy.bar();}
}
public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {Method foo = Foo.class.getMethod("foo");h.invoke(foo, new Object[0]);} catch (Throwable e) {e.printStackTrace();}}@Overridepublic void bar() {try {Method bar = Foo.class.getMethod("bar");h.invoke(bar, new Object[0]);            } catch (Throwable e) {e.printStackTrace();}}
}

        运行结果:

before...
target foo
before...
target bar

        这样我们就基本上解决了调用目标方法的问题了。我们再将方法改进一下,让他能针对有返回值的方法,并将异常的处理也打磨一下:

public class A13 {interface Foo {void foo();int bar();}static class Target implements Foo {@Overridepublic void foo() {System.out.println("target foo");}@Overridepublic int bar() {System.out.println("target bar");return 100;}}interface InvocationHandler {/*** 回调函数* @param proxy 代理类本身* @param method 调用代理的方法* @param args 方法的参数* @return method的返回值*/Object invoke(Object proxy, Method method, Object []args) throws Throwable;}public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1.功能增强System.out.println("before...");// 2.调用目标
//                new Target().foo();Object result = method.invoke(new Target(), args);return result;}});proxy.foo();proxy.bar();}
}
public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {Method foo = Foo.class.getMethod("foo");h.invoke(this, foo, new Object[0]);} catch (RuntimeException | Error e) {// 运行时异常直接抛出throw e;} catch (Throwable e) {// 检查异常,转换成运行时异常,再抛出throw new UndeclaredThrowableException(e);}}@Overridepublic int bar() {try {Method bar = Foo.class.getMethod("bar");Object result = h.invoke(this, bar, new Object[0]);return (int)result;} catch (RuntimeException | Error e) {// 运行时异常直接抛出throw e;} catch (Throwable e) {// 检查异常,转换成运行时异常,再抛出throw new UndeclaredThrowableException(e);}}
}

        现在还有一个小问题:每次我们调用代理类方法的时候,都要去获取一次正在调用的方法:

        这样显然是没必要的,因此我们可以用静态变量和静态代码块,只获取一次方法,就可以一直使用了:

public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {h.invoke(this, foo, new Object[0]);} catch (RuntimeException | Error e) {// 运行时异常直接抛出throw e;} catch (Throwable e) {// 检查异常,转换成运行时异常,再抛出throw new UndeclaredThrowableException(e);}}@Overridepublic int bar() {try {Object result = h.invoke(this, bar, new Object[0]);return (int)result;} catch (RuntimeException | Error e) {// 运行时异常直接抛出throw e;} catch (Throwable e) {// 检查异常,转换成运行时异常,再抛出throw new UndeclaredThrowableException(e);}}static Method foo;static Method bar;static {try {foo = Foo.class.getMethod("foo");bar = Foo.class.getMethod("bar");} catch (NoSuchMethodException e) {//检查异常不能直接抛 要转换成运行时异常再抛//静态代码块出错是很严重的问题  所以我们用error来表示严重的问题throw new NoSuchMethodError(e.getMessage());}}
}

        到此为止,我们的InvocationHandler已经和jdk 的InvocationHandler无异,我们可以直接使用jdk的;代理类也与jdk的只有细微差别,jdk的$Proxy0 会继承Proxy 类,进一步减少代码,像这样:

import java.lang.reflect.InvocationHandler;public class $Proxy0 extends Proxy implements Foo {public $Proxy0(InvocationHandler h) {super(h);}
}

四、jdk实现代理的源码

        JDK动态代理生成的代理类是以字节码的形式存在的,并不存在所谓的.java文件,但也不是说就没办法看到生成的代理类信息了。因此我们需要借助反编译工具(例如:Arthas)才能看到Java代码。 

        下面就是jdk 生成的代理类源码。可以看出跟我们手写的代理类基本一致,只不过JDK生成的代理类信息还生成equals() 、 toString()和hashCode()三个方法对应的Method对象,并对它们也进行了相同的增强。

        经过前面的手写代理类,相信看懂源码已经是十分简单的事情了。

final class $Proxy0 extends Proxy implements Foo {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void foo() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("indi.mofan.a12.JdkProxyDemo$Foo").getMethod("foo");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

五、jdk对代理的优化 

         我们知道使用JDK的动态代理时,会使用反射调用方法:

Object result = method.invoke(target, params);

         相比于正常调用方法,利用反射的性能要稍微低一些,JDK有对反射进行优化吗?我们来实验一下。

/*** @author mofan* @date 2023/1/16 22:36*/
public class TestMethodProxy {public static void main(String[] args) throws Exception {Method foo = TestMethodProxy.class.getMethod("foo", int.class);for (int i = 1; i <= 17; i++) {show(i, foo);foo.invoke(null, i);}System.in.read();}// 方法反射调用时,底层使用了 MethodAccessor 的实现类private static void show(int i, Method foo) throws Exception {Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");getMethodAccessor.setAccessible(true);Object invoke = getMethodAccessor.invoke(foo);if (invoke == null) {System.out.println(i + ":" + null);return;}// DelegatingMethodAccessorImpl 的全限定类名(不同版本的 JDK 存在差异)Field delegate = Class.forName("sun.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");delegate.setAccessible(true);System.out.println(i + ": " + delegate.get(invoke));}public static void foo(int i) {System.out.println(i + ": foo");}
}

        运行结果:

1: null
1: foo
2: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
2: foo
3: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
3: foo
4: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
4: foo
5: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
5: foo
6: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
6: foo
7: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
7: foo
8: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
8: foo
9: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
9: foo
10: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
10: foo
11: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
11: foo
12: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
12: foo
13: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
13: foo
14: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
14: foo
15: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
15: foo
16: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
16: foo
17: sun.reflect.GeneratedMethodAccessor2@5b2133b1
17: foo

        从上述信息可知,第一次调用时没有使用MethodAccessor对象,是因为这个时候刚刚生成;从第二次到第十六次,使用了NativeMethodAccessorImpl对象,而在第十七次使用了GeneratedMethodAccessor2对象。

        NativeMethodAccessorImpl 基于Java本地API实现,性能较低,第十七次调用换成GeneratedMethodAccessor2后,性能得到一定的提升。

        使用Arthas反编译查看 GeneratedMethodAccessor2类中的信息,内容如下:

public class GeneratedMethodAccessor2 extends MethodAccessorImpl {/** Loose catch block*/public Object invoke(Object object, Object[] objectArray) throws InvocationTargetException {// --snip--try {// 正常调用方法TestMethodProxy.foo((int)c);return null;}catch (Throwable throwable) {throw new InvocationTargetException(throwable);}catch (ClassCastException | NullPointerException runtimeException) {throw new IllegalArgumentException(super.toString());}}
}

        在反编译得到的代码中,不再是通过反射调用方法,而是直接正常调用方法,即:

TestMethodProxy.foo((int)c);

         因此性能得到了提升,但这样的提升也是有一定代价的:仅仅为优化一个方法的反射调用,就生成了一个GeneratedMethodAccessor2代理类。 

六、cglib实现动态代理的原理

        看懂了jdk,可以发现cglib跟它大同小异。我们跟学习jdk的时候一样,手写代理类来加深对原理的理解。

        先准备一个目标类。

public class Target {public void save() {System.out.println("save()");}public void save(int i) {System.out.println("save(int)");}public void save(long j) {System.out.println("save(long)");}
}

         然后是代理类。jdk通过 InvocationHandler来实现增强逻辑,cglib通过 MethodInterceptor实现增强逻辑,两者区别不大,我们可以看看MethodInterceptor 的源码:

public interface MethodInterceptor extends Callback {Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

        这是一个接口,我们在测试类中实现它。

        下面是我们实现的代理类: 

public class Proxy extends Target{//jdk通过InvocationHandler来实现增强逻辑,cglib通过MethodInterceptor实现增强逻辑private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long j) {try {methodInterceptor.intercept(this, save2, new Object[]{j}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}

        在主函数中测试一下:

public class A14 {public static void main(String[] args) {Target target = new Target();Proxy proxy = new Proxy();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 功能增强System.out.println("before...");// 反射调用目标return method.invoke(target, args);}});proxy.save();proxy.save(1);proxy.save(2L);}
}

        运行结果:

before...
save()
before...
save(int)
before...
save(long)

七、cglib动态代理如何避免反射——methodProxy原理

7.1  methodProxy的使用

        要使用methodProxy,我们不仅需要带有增强功能的方法,还需要仅带有原始功能的方法。

public class Proxy extends Target{//jdk通过InvocationHandler来实现增强逻辑,cglib通过MethodInterceptor实现增强逻辑private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有原始功能的方法//因为不能和save同名,我们换个名字,换成saveSuperpublic void saveSuper() {super.save();}public void saveSuper(int i) {super.save(i);}public void saveSuper(long j) {super.save(j);}//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有增强功能的方法@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long j) {try {methodInterceptor.intercept(this, save2, new Object[]{j}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}

        调用MethodProxy.create() 方法创建一个MethodProxy对象,我们也像处理Method对象一样,用一个静态成员变量来接收。创建好了之后,将这个静态变量传入intercept方法中即可

public class Proxy extends Target{//jdk通过InvocationHandler来实现增强逻辑,cglib通过MethodInterceptor实现增强逻辑private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static MethodProxy save0Proxy;static MethodProxy save1Proxy;static MethodProxy save2Proxy;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);// create的参数:1、目标类;2、代理类;3、增强方法的描述;4、增强方法名;5、原始方法名//()V : ()表示入参是空,V表示返回值是voidsave0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有原始功能的方法//因为不能和save同名,我们换个名字,换成saveSuperpublic void saveSuper() {super.save();}public void saveSuper(int i) {super.save(i);}public void saveSuper(long j) {super.save(j);}//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有增强功能的方法@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long j) {try {methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}

        在主函数中的使用是我们本文二中已经讲到的:

public class A14 {public static void main(String[] args) {Target target = new Target();Proxy proxy = new Proxy();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 功能增强System.out.println("before...");// 反射调用
//                return method.invoke(target, args);// 结合目标对象来调用,内部没有用反射
//                return methodProxy.invoke(target, args);// 结合代理对象来调用,内部没有用反射return methodProxy.invokeSuper(p, args);}});proxy.save();proxy.save(1);proxy.save(2L);}
}

7.2 methodProxy不使用反射的原理

        我们知道,methodProxy.invoke 和 methodProxy.invokeSuper 是不需要反射的,那么他们的原理是什么呢?这与FastClass有关,FastClass本质上也是代理类,在他的实现类中的一些关键方法能够做到无需反射。而FastClass也是直接生成的字节码,我们也无法看到他的源码,因此我们来模拟一下实现FastClass。

         先模拟实现TargetFastClass。

         当调用了MethodProxy.create的时候,就会根据字节码生成对应的TargetFastClass,就可以根据TargetFastClass 中的getIndex方法获得方法对应的编号;而在调用Method.invoke方法时,内部会调用TargetFastClass的invoke 方法,这样就能知道要调用哪个方法了,而无需反射调用。

public class TargetFastClass extends FastClass {//先定义好编号对应的方法签名static Signature s0 = new Signature("save", "()V");static Signature s1 = new Signature("save", "(I)V");static Signature s2 = new Signature("save", "(J)V");/*** 获取目标方法的编号。我们可以事先规定好Target类中所有方法对应的编号,通过此方法拿到*      例如,Target中的方法,我们可以这样编号:*          save()          0*          save(int i)     1*          save(long j)    2** @param signature 方法签名* @return 方法编号*/@Overridepublic int getIndex(Signature signature) {if (s0.equals(signature)) {//返回方法编号0return 0;} else if (s1.equals(signature)) {return 1;} else if (s2.equals(signature)) {return 2;}return -1;}/*** 根据getIndex 返回的方法编号,正常调用对应方法* @param i getIndex返回的方法编号* @param o 目标对象* @param objects 方法的参数* @return 方法的返回结果* @throws InvocationTargetException*/@Overridepublic Object invoke(int i, Object o, Object[] objects) throws InvocationTargetException {if (i == 0) {((Target)o).save();return null;} else if (i == 1) {((Target)o).save((Integer) objects[0]); return null;} else if (i == 2) {((Target)o).save((Long) objects[0]);return null;} else {throw new RuntimeException("没有此方法");}}//>>>>>>>>>>>>>>>>>>下面是相对来说不重要的方法,就不研究了@Overridepublic int getIndex(String s, Class[] classes) {return 0;}@Overridepublic int getIndex(Class[] classes) {return 0;}@Overridepublic Object newInstance(int i, Object[] objects) throws InvocationTargetException {return null;}@Overridepublic int getMaxIndex() {return 0;}
}

        同样地,也有和Proxy配合的ProxyFastClass:

public class ProxyFastClass {// 这里应该用原始方法,因为我们这里是需要配合MethodProxy.invoke方法,在此方法之前已经进行了功能增强,所以只需要原始方法即可// 同时,这里不能用增强的方法,只能用原始方法,因为增强方法中本身要调用intercept,而intercept方法中又需要用到ProxyFastClass,会形成死循环static Signature s0 = new Signature("saveSuper", "()V");static Signature s1 = new Signature("saveSuper", "(I)V");static Signature s2 = new Signature("saveSuper", "(J)V");// 获取代理方法的编号/*ProxysaveSuper()              0saveSuper(int)           1saveSuper(long)          2signature 包括方法名字、参数返回值*/public int getIndex(Signature signature) {if (s0.equals(signature)) {return 0;} else if (s1.equals(signature)) {return 1;} else if (s2.equals(signature)) {return 2;}return -1;}// 根据方法编号, 正常调用目标对象方法public Object invoke(int index, Object proxy, Object[] args) {if (index == 0) {((Proxy) proxy).saveSuper();return null;} else if (index == 1) {((Proxy) proxy).saveSuper((int) args[0]);return null;} else if (index == 2) {((Proxy) proxy).saveSuper((long) args[0]);return null;} else {throw new RuntimeException("没有此方法");}}
}

 7.3 与jdk反射优化的性能对比

        jdk当中也对反射做了性能优化,原理与cglib大致相同,不过jdk需要进行16次调用,从第17次开始才做优化,并且针对一个方法产生一个代理类。

        cglib的优化在于,在第一次调用的时候就无需反射,并且一个代理类对应生成两个FastClass(本质上也是代理类),一个用于配合目标类,一个用于配合代理类,一个FastClass中会对应多个方法。总体而言,生成代理类的数量会比jdk少。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_286161.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ABP vNext电商项目落地实战(一)

一、落地条件&#xff1a; 1. .NET5版本 2. DDD 3. ABP vNext 4.ABP CLI &#xff08;ABP的命令行工具&#xff0c;包括ABP的各种模板&#xff09; 5.SQL Server 写在前面&#xff1a;我觉得这个框架的文件分层很凌乱&#xff0c;在企业的实际业务场景中&#xff0c;一般…

RHCSA练习作业(二)

目录 题目一 题目二 题目三 第四题 第五题 题目一 文件查看&#xff1a;查看/opt/passwd文件的第六行&#xff08;使用head和tail指令&#xff09; 代码如下&#xff1a; head -6 /opt/passwd | tail -1 题目二 在/etc及其子目录中&#xff0c;查找host开头的文件&#x…

虚拟机Ubuntu 18.04安装dpdk18.11

1 虚拟机用了2个核&#xff0c;2G内存&#xff0c;3个网卡 2 去http://core.dpdk.org/download/下载了18.11 3 安装apt-get install libnuma-dev 4 运行ifconfig down ***停掉2个网卡&#xff0c;方便后面添加到dpdk 5 解压后&#xff0c;进入usertools&#xff0c;运行dpd…

用AI帮我写一篇关于FPGA的文章,并推荐最热门的FPGA开源项目

FPGA定义 FPGA&#xff08;Field Programmable Gate Array&#xff09;是一种可编程逻辑器件&#xff0c;可以在硬件电路中实现各种不同的逻辑功能。与ASIC&#xff08;Application Specific Integrated Circuit&#xff0c;特定应用集成电路&#xff09;相比&#xff0c;FPGA…

SQL 条件函数 日期函数 文本函数 窗口函数

玩了几天&#xff0c;劳逸结合&#xff0c;继续复习刷题sql 一、条件函数 1.题目&#xff1a;现在运营想要将用户划分为25岁以下和25岁及以上两个年龄段&#xff0c;分别查看这两个年龄段用户数量&#xff08;age为null 也记为 25岁以下&#xff09; user_profile 期望结果&…

由浅入深掌握Python多线程原理与编程步骤

由浅入深掌握Python多线程编程一、 Python多线程编程原理1. 什么是线程2. 线程工作原理3. Python全局锁与线程关系4. Python 支持多线程的模块二、由简单的示例初步了解多线程编程步骤三、标准库 threading 模块介绍1. threading 模块的主要属性、方法&#xff0c;以及公共函数…

C++ [图论算法详解] 欧拉路欧拉回路

蒟蒻还在上课&#xff0c;所以文章更新的实在慢了点 那今天就来写一篇这周刚学的欧拉路和欧拉回路吧 讲故事环节&#xff1a; 在 一个风雪交加的夜晚 18世纪初普鲁士的哥尼斯堡&#xff0c;有一条河穿过&#xff0c;河上有两个小岛&#xff0c;有七座桥把两个岛与河岸联系…

Python手写板 画图板 签名工具

程序示例精选 Python手写板 画图板 签名工具 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<Python手写板 画图板 签名工具>>编写代码&#xff0c;代码整洁&#xff0c;规则&am…

Diffusion模型系列文章

DDPM 论文 扩散模型包括两个过程&#xff1a;前向过程&#xff08;forward process&#xff09;和反向过程&#xff08;reverse process&#xff09;&#xff0c;其中前向过程又称为扩散过程&#xff08;diffusion process&#xff09;&#xff0c;如下图所示&#xff0c;从x…

如何定位Spark数据倾斜问题,解决方案

文章目录前言一、数据倾斜和数据过量二、 数据倾斜的表现三、定位数据倾斜问题定位思路&#xff1a;查看任务-》查看Stage-》查看代码四、7种典型的数据倾斜场景解决方案一&#xff1a;聚合元数据解决方案二&#xff1a;过滤导致倾斜的key解决方案三&#xff1a;提高shuffle操作…

1.docker-安装及使用

1.安装步骤 Install Docker Engine on CentOS 1. 确定CenOS7及以上版本 cat /etc/redhat-release2.卸载旧版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine3.yum安…

Spimes x5.0主题模板全开源源码/Typecho主题模板

☑️ 品牌&#xff1a;Typecho ☑️ 语言&#xff1a;PHP ☑️ 类型&#xff1a;主题模板 ☑️ 支持&#xff1a;PCWAP &#x1f389;有需要的朋友记得关赞评&#xff0c;底部分享获取&#xff01;&#xff01;&#xff01; &#x1f389; ✨ 源码介绍 Spimes x5.0主题模板全开…

基于overleaf 的美国大学生数学建模竞赛(美赛)latex 格式模板(含信件和附件)

可能是最后一次打美赛了&#xff0c;感觉有的东西不整理整理有点对不起自己的经历。感觉为这个比赛付出过挺多的&#xff0c;这几次参赛的经历也从各种方面提升了我的能力&#xff0c;相信未来的自己也还会怀念这段时光。 个人认为美赛的难点之一就是优质资源难得&#xff0c;…

Pytorch深度学习笔记(三)线性模型

目录 1.机械学习的过程 2.线性模型 1.机械学习的过程 机械学习的过程&#xff1a; 1.准备数据集DataSet——>2.选择模型Model——>3.训练Training——>4.推理Infering 监督学习&#xff1a;用已知标签的训练样本训练模型&#xff0c;用来预测未来输入样本的标签&#…

Android---内存泄漏检测核心原理

目录 LeakCanary 核心原理 LeakCanary 检测对象的类型 ReferenceQueue 与 WeakReference LeakCanary 里的监控列表与保留列表 常见内存泄漏案例 1. 单例导致内存泄漏 2. 静态变量导致内存泄漏 3. 非静态内部类导致内存泄漏 4. 未取消注册或回调导致内存泄漏 5. Timer…

ChatGPT的发展对客户支持能提供什么帮助?

多数组织认为客户服务是一种开销&#xff0c;实际上还可以将客户服务看成是一种机会。它可以让你在销售后继续推动客户的价值。成功的企业深知&#xff0c;客户服务不仅可以留住客户&#xff0c;还可以增加企业收入。客户服务是被低估的手段&#xff0c;它可以通过推荐、见证和…

AI绘画与虚拟人生成实践(一):生成人像,AI绘画模型和工具的效果对比

本篇的目的是生成一个虚拟的女生形象。先进入正题说明人像怎么生成,本篇使用到的工具和工具的介绍放在文末。 先来一波Midjourney生成的美图提升下大家学习的欲望 以上四张图使用的是相同的Prompt,如下: a beautiful chinese girl, 18 years old, detailed and big eye…

【c++初阶】命名空间的定义

命名空间的定义一.缺陷二.namespace和::三.访问namespace四.一些注意1.工程里标准库的展开2.命名域的小技巧一.缺陷 在c语言中&#xff0c;如果我们同时定义一个全局变量和一个局部变量并且使用同一个名称的话&#xff0c;是可以编过的&#xff08;因为全局和局部是属于两个不同…

算法训练Day25:216.组合总和III ,17.电话号码的字母组合

文章目录[组合总和 III](https://leetcode.cn/problems/combination-sum-iii/description/)题解电话号码的字母组合题解组合总和 III CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsMedium (71.84%)6570--0 TagsCompanies 找出所有相加之和为 n 的 …

分子生物学 第五章 DNA损伤修复和突变

文章目录第五章 DNA损伤修复和突变第一节第二节 DNA损伤的类型1 造成DNA损伤的因素2 DNA损伤的类型3 DNA损伤修复机制3.1 直接修复3.2 切除修复3.3 双链断裂修复3.4 重组修复3.5 跨越合成第五章 DNA损伤修复和突变 第一节 损伤&#xff1a;比如碱基&#xff0c;甲基化 突变&…