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

news2024/11/17 15:31:16

目录

一、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可以转换为Target
        Target 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可以转换为Target
        Target 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可以转换为Target
        Target 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 {

        @Override
        public void foo() {
            System.out.println("target foo");
        }
    }

    public static void main(String[] args) {
        
    }
}

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

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

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

public class A13 {

    interface Foo {
        void foo();
    }

    static class Target implements Foo {

        @Override
        public 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 {

        @Override
        public void foo() {
            System.out.println("target foo");
        }
    }

    interface InvocationHandler {
        void invoke();
    }

    public static void main(String[] args) {
        Foo proxy = new $Proxy0(new InvocationHandler() {
            @Override
            public 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;
    }

    @Override
    public void foo() {
        h.invoke();
    }
}

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

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

public class A13 {

    interface Foo {
        void foo();
        void bar();
    }

    static class Target implements Foo {

        @Override
        public void foo() {
            System.out.println("target foo");
        }

        @Override
        public 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() {
            @Override
            public 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;
    }

    @Override
    public void foo() {
        try {
            Method foo = Foo.class.getMethod("foo");
            h.invoke(foo, new Object[0]);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    @Override
    public 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 {

        @Override
        public void foo() {
            System.out.println("target foo");
        }

        @Override
        public 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() {
            @Override
            public 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;
    }

    @Override
    public 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);
        }
    }

    @Override
    public 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;
    }

    @Override
    public void foo() {
        try {
            h.invoke(this, foo, new Object[0]);
        } catch (RuntimeException | Error e) {
            // 运行时异常直接抛出
            throw e;
        } catch (Throwable e) {
            // 检查异常,转换成运行时异常,再抛出
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public 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());
        }
    }

    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public 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() {
            @Override
            public 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同名,我们换个名字,换成saveSuper
    public void saveSuper() {
        super.save();
    }
    public void saveSuper(int i) {
        super.save(i);
    }
    public void saveSuper(long j) {
        super.save(j);
    }

    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public 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表示返回值是void
            save0Proxy = 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同名,我们换个名字,换成saveSuper
    public void saveSuper() {
        super.save();
    }
    public void saveSuper(int i) {
        super.save(i);
    }
    public void saveSuper(long j) {
        super.save(j);
    }

    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public 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() {
            @Override
            public 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 方法编号
     */
    @Override
    public int getIndex(Signature signature) {
        if (s0.equals(signature)) {
            //返回方法编号0
            return 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
     */
    @Override
    public 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("没有此方法");
        }
    }

    //>>>>>>>>>>>>>>>>>>下面是相对来说不重要的方法,就不研究了
    @Override
    public int getIndex(String s, Class[] classes) {
        return 0;
    }
    @Override
    public int getIndex(Class[] classes) {
        return 0;
    }
    @Override
    public Object newInstance(int i, Object[] objects) throws InvocationTargetException {
        return null;
    }
    @Override
    public 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");

    // 获取代理方法的编号
    /*
        Proxy
            saveSuper()              0
            saveSuper(int)           1
            saveSuper(long)          2
        signature 包括方法名字、参数返回值
     */
    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.coloradmin.cn/o/438238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(Linux驱动入门)字符设备

一、设备相关概念 1.1 设备号 内核中通过类型dev_t来描述设备号&#xff0c;其实质是unsigned int 32位整数&#xff0c;其中高12位为主设备号&#xff0c;低20位为次设备号。设备号也是一种资源&#xff0c;当我们需要时可以调用函数去申请。 ​​​​​​​int register_c…

光伏发电数据监控的运维平台

摘要&#xff1a;全球化经济社会的快速发展,加快了传统能源的消耗,导致能源日益短缺,与此同时还带来了严重的环境污染。因此,利用没有环境污染的太阳能进行光伏发电获得了社会的普遍关注。本文根据传统式光伏电站行业的发展背景及其监控系统的技术设备,给出了现代化光伏电站数据…

Vue3通透教程【十二】TS类型声明优势

文章目录 &#x1f31f; 写在前面&#x1f31f; 上篇文章解惑&#x1f31f; JS函数中的隐患&#x1f31f; 函数中的类型&#x1f31f; 写在最后 &#x1f31f; 写在前面 专栏介绍&#xff1a; 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章&#xff0c;应粉丝要求开始更新 V…

计算机视觉 | 八斗人工智能 (中)

目录 卷积&滤波1.一个没有任何效果的卷积核2.平均均值滤波3.图像锐化4.soble边缘检测 卷积的三种填充模式1.padding --> same模式 最常用的模式2.full和valid模式三通道卷积 canny边缘检测算法&#xff08;效果最好&#xff09;Sobel算子、Prewitt算子 相机模型畸变矫正…

新能源汽车和数字化转型

工业时代的代表产品是交通运输设备&#xff0c;核心桂冠是发动机。信息时代的代表产品是智能手机&#xff0c;核心桂冠是芯片。 汽车是个很有代表性产品&#xff0c;因为它既属于复杂高精密金属机械设备&#xff0c;又属于大规模使用的大件消费品。所以这100年来&#xff0c;汽…

代码随想录算法训练营第三十二天|122.买卖股票的最佳时机II 、55. 跳跃游戏 、45.跳跃游戏II

文章目录 122.买卖股票的最佳时机II55. 跳跃游戏45.跳跃游戏II:star: 122.买卖股票的最佳时机II 遇到每天正利润就收集&#xff0c;负利润就不收集 链接:代码随想录 解题思路&#xff1a; ①因为可以多次买卖&#xff0c;所以考虑到最终把最终利润进行分解 如假如第0天买入&am…

垃圾收集算法面试总结

垃圾收集算法 标记 - 清除算法 首先标记出所有需要被回收的对象&#xff0c;标记完后统一回收所有被标记的对象。 后续的收集算法都是基于这种思路并对其不足进行改进而得到的。 这种方法主要有两个缺点&#xff1a; 一个是效率问题&#xff0c;标记和清除两个过程的效率都…

java mysql超市会员积分带抽奖系统

后台相关操作&#xff1a; &#xff08;1&#xff09;系统管理&#xff1a;管理系统的管理员用户。 &#xff08;2&#xff09;会员管理&#xff1a;对会员信息进行增删改功能。 &#xff08;3&#xff09;商品管理&#xff1a;对系统的商品进行增删改查功能等维护。 &#xff…

分治法解二维的最近对问题,算法分析与代码实现,蛮力法与分治法解决二维的最近对问题的区别

&#x1f38a;【数据结构与算法】专题正在持续更新中&#xff0c;各种数据结构的创建原理与运用✨&#xff0c;经典算法的解析✨都在这儿&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 -…

人工智能发展到GPT4经历了什么,从专家系统到机器学习再到深度学习,从大模型到现在的GPT4

大家好&#xff0c;我是微学AI&#xff0c;今天给大家讲一下人工智能的发展&#xff0c;从专家系统到机器学习再到深度学习&#xff0c;从大模型到现在的GPT4&#xff0c;讲这个的目的是让每个人都懂得人工智能&#xff0c;每个人都懂得人工智能的发展&#xff0c;未来人工智能…

“智慧赋能 强链塑链”—— 煤炭行业数字化转型探讨

煤炭作为传统能源行业之一&#xff0c;是国民经济中不可或缺的一部分&#xff0c;随着国家能源结构的战略转型&#xff0c;煤炭企业的长期盈利能力将面临巨大的挑战。供应链作为煤炭行业生产运营的基础保障&#xff0c;在企业开源节流的要求下&#xff0c;其传统粗放的供应链管…

Xcode 14.3 cocoapod 1.12.0 打包报错解决

前言 前几天升级Xcode到14.3版本&#xff0c;运行项目报错&#xff0c;于是记录下来。 开发环境 macOS: 13.3.1 Xcode: 14.3 CocoaPods: 1.12.0 问题描述 [Xcode菜单栏] -> [Product] -> [Archive]&#xff0c;进行打包操作。执行到 Run custom shell script [CP]…

day16 信号灯

信号灯概念和有名信号灯 目录 信号灯概念和有名信号灯 有名信号灯 无名信号灯 信号灯P操作 信号灯V操作 system V信号灯的 信号灯/信号量&#xff08;semaphore&#xff09; 信号量代表某一类资源&#xff0c;其值表示系统中该资源的数量&#xff1b; 信号量是一个受保…

【C语言】程序运行环境及预处理指令

文章目录 程序的翻译环境&#xff1a;程序的运行环境&#xff1a;C语言预定义符号#define定义标识符#define定义宏具有副作用的宏参数 #与###的使用##的使用 宏和函数对比#undef命令行定义条件编译常见的条件编译指令&#x1f31e; 文件包含指令嵌套文件包含 其他预处理指令 撒…

【C++】对数组指针的理解,例如 int (*p)[3]

目录 简介思考理解结语 简介 Hello&#xff01; 非常感谢您阅读海轰的文章&#xff0c;倘若文中有错误的地方&#xff0c;欢迎您指出&#xff5e; ଘ(੭ˊᵕˋ)੭ 昵称&#xff1a;海轰 标签&#xff1a;程序猿&#xff5c;C选手&#xff5c;学生 简介&#xff1a;因C语言结识…

Win7 无法安装 VMware Tools 解决方法

文章目录 1.下载kb4474419补丁2.虚拟机 > 设置 > 软盘&#xff0c;选中“使用物理驱动器”3.解决IE浏览器只能访问百度4.下载windows iso的正确方式 win7版本&#xff1a;cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408_2&#xff08;iso大小3.18 GB&#xff09; vmwa…

C++内联函数(编译器角度,汇编角度),auto关键字,范围for语法糖,nullprt与NULL区别等

TIPS 在C当中有一个东西可以打印类型&#xff1f;typeid(变量名).name()数组一旦从参数进入到函数里面&#xff0c;它就已经是个指针了&#xff0c;再也不是一整个数组了 内联函数&#xff08;正常函数定义前加个inline修饰&#xff09; 在实际当中&#xff0c;有时候去调用…

13、拦截器

文章目录 1、HandlerInterceptor 接口2、配置拦截器3、拦截器原理 【尚硅谷】SpringBoot2零基础入门教程-讲师&#xff1a;雷丰阳 笔记 路还在继续&#xff0c;梦还在期许 1、HandlerInterceptor 接口 /*** 登录检查* 1、配置好拦截器要拦截哪些请求* 2、把这些配置放在容器中…

爬虫——肯德基

import requests #UA伪装&#xff1a;将对应的User-Agent封装到一个字典中 headers{User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.48 } #指定url url http://www.kfc.com.c…

【jvm系列-09】垃圾回收底层原理和算法以及JProfiler的基本使用

JVM系列整体栏目 内容链接地址【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963【三】运行时私有区域之虚拟机栈…