java基础进阶七-JDK动态代理

一个需求:给原有方法添加日志打印

假设你刚进入一个项目组,项目中存在一个Calculator类,代表一个计算器,它可以进行加减乘除操作:

public class Calculator {

	// 加
	public int add(int a, int b) {
		int result = a + b;
		return result;
	}

	// 减
	public int subtract(int a, int b) {
		int result = a - b;
		return result;
	}

	// 乘法、除法...
}

现在老大给你提了一个需求:在每个方法执行前后打印日志。

方案一:直接修改
public class Calculator {

	// 加
	public int add(int a, int b) {
		System.out.println("add方法开始...");
		int result = a + b;
		System.out.println("add方法结束...");
		return result;
	}

	// 减
	public int subtract(int a, int b) {
		System.out.println("subtract方法开始...");
		int result = a - b;
		System.out.println("subtract方法结束...");
		return result;
	}

	// 乘法、除法...
}

上面的方案是有问题的:

  1. 直接修改源程序,不符合开闭原则,即好的程序设计应该对扩展开放,对修改关闭
  2. 如果类中有很多类,修改量大
  3. 存在重复代码(都是在核心代码前后打印日志)
  4. 日志打印硬编码在代理类中,不利于后期维护:比如你花了一上午终于写完了,组长告诉你这个功能不做了,于是你又要打开Calculator花十分钟删除日志打印的代码(或回滚分支)!

pass!!!

方案二:静态代理实现日志打印

先理解一下什么是代理:
代理是一种模式,提供了对目标对象的简介访问方式,即通过代理访问目标对象,如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求。
在这里插入图片描述
常用的代理方式可以粗分为:静态代理动态代理

静态代理的实现比较简单:编写一个代理类,实现于目标对象相同的接口,并在内部维护一个目标对象的引用,通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。

抽取接口

public interface Calculator {
	int add(int a, int b);
	int subtract(int a, int b);
}

原目标类实现接口

/**
 * 目标类,实现Calculator接口(如果一开始就面向接口编程,其实是不存在这一步的,CalculatorImpl原本就实现Calculator接口)
 */
public class CalculatorImpl implements Calculator {

	// 加
	public int add(int a, int b) {
		int result = a + b;
		return result;
	}

	// 减
	public int subtract(int a, int b) {
		int result = a - b;
		return result;
	}

	// 乘法、除法...
}

新增代理类并实现接口

/**
 * 静态代理类,实现Calculator接口
 */
public class CalculatorProxy implements Calculator {
    // 代理对象内部维护一个目标对象引用
	private Calculator target;
        
    // 通过构造方法,传入目标对象
	public CalculatorProxy(Calculator target) {
		this.target = target;
	}

    // 调用目标对象的add,并在前后打印日志
	@Override
	public int add(int a, int b) {
		System.out.println("add方法开始...");
		int result = target.add(a, b);
		System.out.println("add方法结束...");
		return result;
	}

    // 调用目标对象的subtract,并在前后打印日志
	@Override
	public int subtract(int a, int b) {
		System.out.println("subtract方法开始...");
		int result = target.subtract(a, b);
		System.out.println("subtract方法结束...");
		return result;
	}

	// 乘法、除法...
}

测试案例

public class Test {
	public static void main(String[] args) {
		// 把目标对象通过构造器塞入代理对象
		Calculator calculator = new CalculatorProxy(new CalculatorImpl());
		// 代理对象调用目标对象方法完成计算,并在前后打印日志
		calculator.add(1, 2);
		calculator.subtract(2, 1);
	}
}  
静态代理的问题

上面的代码中,为了给目标类做日志增强,我们编写了代理类,而且准备了一个构造器接收目标对象。代理代理对象构造器的参数类型是Calculator,这意味着它只能接受Calculator的实现类对象,亦即我们写的代理类CalculatorProxy只能给Calculator做代理,它们绑定死了!

如果现在我们系统需要全面改造,要给其他类也添加日志打印功能,就得为其他几百个接口都各自写一份代理类…

自己手动写一个类并实现接口实在太麻烦了。仔细一想,我们其实想要的并不是代理类,而是代理对象!

换言之,静态代理的解耦能力还是太薄弱了,要想对源程序实现不同的增强功能,必须编写不同的代理类,有多少种增强需求,就要写多少个静态代理类!

我们的诉求是:增强代码我可以写(这个省不了,不然鬼知道你要打印日志还是啥),但代理类能不能不写?

要想完成上面的诉求,至少需要解决两个问题:
● 自动生成代理对象,让程序员免受编写代理类的痛苦
● 将增强代码与代理类(代理对象)解耦,从而达到代码复用(可插拔式的增强,给我增强的代码,我就返回一个实现了该增强的代理对象)

思考过程:如何自动生成代理对象
复习对象的创建

所谓“万物皆对象”,字节码文件也难逃“被对象”的命运。当字节码文件(.class文件)被加载进内存后,JVM也为其创建了一个对象,以后所有该类的实例,皆以它为模板。这个对象叫Class对象,它是Class类的实例。
在这里插入图片描述
可以看出,要创建一个实例,最关键的就是得到对应的Class对象。只不过对于初学者来说,new这个关键字配合构造方法,实在太好用了,底层隐藏了太多细节,一句 Person p = new Person();直接把对象返回给你了。

回到之前的问题上:如何不写代理类,直接得到代理对象。

按照上面的截图:代理类和实例对象之间其实还隔着一个Class对象,如果能得到Class对象,就能生成实例。所以,现在的问题又变成:如何不写代理类,直接得到Class对象。

Class类与Class对象

要得到Class对象,就要先明白Class对象是什么,又是怎么来的。

这有一个很重要的概念:Class类。

类是用来描述一类事物的,我们有Person类描述“人”,Student类描述“学生”,而Class类就是用来描述“类”的。是不是觉得有点绕?换句话说:

类可以用来描述任意事物,所以理论上我们也能定义一个类,用来描述类本身,但这个Class类不需要我们自己写,JDK已经帮我们定义好了,放在java.lang包下。Class类、Person类、Student类本质相同,只不过Class类描述的东西比较特殊罢了。

在这里插入图片描述
理论上,只要编写了类,那么通过JVM通常可以得到该类的对象,Class类的对象就是Class对象。
那么Person类的实例对象是Person p,那么Person类的Class对象怎么表示?

在这里插入图片描述
Person类有两个不同维度的对象:
● 根据Person类实例化得到的Person p1、 p2、p3对象
● Class类实例化得到的Class personClass对象

第一个“对象”好理解,就是我们经常new的那种对象,关键是Class对象。Class类只有一个,却要描述形形色色的各种类,比如Person类、Student类,那么如何区分谁是谁的Class对象呢?

答案就是泛型。

Class类是泛型类,JDK利用泛型区分不同的Class对象,比如Class personClass、Class studentClass。

Class对象只能由JVM创建。虽然不能new,但Java还是提供了其他方式让我们得到Class对象,底层会告诉JVM帮我们创建:

  1. Class.forName(xxx):Class clazz = Class.forName(“com.bravo.Person”);
  2. xxx.class:Class clazz = Person.class;
  3. xxx.getClass():Class clazz = person.getClass();

OK,学到这,我们已经了解了Class对象到底是什么,以及得到Class对象的三种常见方式。但是,这三种方式都需要先有类,但我们不想编写代理类!

从接口寻求突破口!

仔细想一下,代理类或者代理对象重要吗?它几乎是个空壳,最重要的其实是 增强代码 + 目标对象。换句话说,我们对代理对象的要求很低,只需要与目标对象拥有相同的方法即可。如此一来,别人调用proxy.add()得到的效果和调用target.add()是一样的,甚至因为两者都实现了相同接口,用接口类型接收后,calculator.add()根本分不出是代理还是原对象。

在这里插入图片描述
所以本质上,代理对象只要有方法申明即可,甚至不需要方法体,或者只要一个空的方法体即可,反正我们会把目标对象返回去。

那么,如何知道一个类有哪些方法信息呢?如果能得到类的方法信息,我们或许可以直接造一个代理对象。

有两个途径:
● 目标类本身
● 目标类实现的接口

这两个思路造就了两种不同的代理机制,一个被后人称为CGLib动态代理,另一个则被JDK收录,世人称之为JDK动态代理。本文重点介绍JDK动态代理。

我们先来验证一下,接口是否真的包含我们需要的方法信息:

public class ProxyTest {
    public static void main(String[] args) {
        /**
         * Calculator接口的Class对象
         * 得到Class对象的三种方式:
         * 1.Class.forName(xxx)
         * 2.xxx.class
         * 3.xxx.getClass()
         * 注意,这并不是我们new了一个Class对象,而是让虚拟机加载并创建Class对象
         */
        Class<Calculator> calculatorClazz = Calculator.class;
        //Calculator接口的构造器信息
        Constructor<?>[] calculatorClazzConstructors = calculatorClazz.getConstructors();
        //Calculator接口的方法信息
        Method[] calculatorClazzMethods = calculatorClazz.getMethods();
        //打印
        System.out.println("------接口Class的构造器信息------");
        printClassInfo(calculatorClazzConstructors);
        System.out.println("\n");
        System.out.println("------接口Class的方法信息------");
        printClassInfo(calculatorClazzMethods);
        System.out.println("\n");

		/**
		 * Calculator实现类的Class对象
		 */
		Class<CalculatorImpl> calculatorImplClazz = CalculatorImpl.class;
        //Calculator实现类的构造器信息
        Constructor<?>[] calculatorImplClazzConstructors = calculatorImplClazz.getConstructors();
        //Calculator实现类的方法信息
        Method[] calculatorImplClazzMethods = calculatorImplClazz.getMethods();
        //打印
        System.out.println("------实现类Class的构造器信息------");
        printClassInfo(calculatorImplClazzConstructors);
        System.out.println("\n");
        System.out.println("------实现类Class的方法信息------");
        printClassInfo(calculatorImplClazzMethods);
    }

    public static void printClassInfo(Executable[] targets) {
        for (Executable target : targets) {
            // 构造器/方法名称
            String name = target.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            // 拼接左括号
            sBuilder.append('(');
            Class<?>[] clazzParams = target.getParameterTypes();
            // 拼接参数
            for (Class<?> clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(',');
            }
            //删除最后一个参数的逗号
            if (clazzParams.length != 0) {
                sBuilder.deleteCharAt(sBuilder.length() - 1);
            }
            //拼接右括号
            sBuilder.append(')');
            //打印 构造器/方法
            System.out.println(sBuilder.toString());
        }
    }
}

在这里插入图片描述

得到以下结论:
● 接口Class对象没有构造方法,所以Calculator接口不能直接new对象
● 实现类Class对象有构造方法,所以CalculatorImpl实现类可以new对象
● 接口Class对象有两个方法add()、subtract()
● 实现类Class对象除了add()、subtract(),还有从Object继承的方法

至此,我们至少知道从接口获取方法信息是可能的!接下来的努力方向就是:怎么根据一个接口得到代理对象。

引入JDK动态代理

通过上面的实验,我们知道了接口确实包含了我们需要的方法信息,而且还知道接口缺少构造器信息。那么,是否存在一种机制,能给接口安装上构造器呢?或者,不改变接口本身,直接拷贝接口的信息到另一个Class,然后给那个Class装上构造器呢?

很显然,不论是从开闭原则还是常规设计考虑,直接修改接口Class的做法相对来说不是很合理。JDK选择了后者:拷贝接口Class的信息,产生一个新的Class对象。

也就是说,JDK动态代理的本质是:用Class造Class,即用接口Class造出一个代理类Class。
在这里插入图片描述
在这里插入图片描述
Proxy.getProxyClass():返回代理类的Class对象。

也就说,只要传入接口的Class对象,getProxyClass()方法即可返回代理Class对象,而不用实际编写代理类。这相当于什么概念?
在这里插入图片描述

public class ProxyTest {
    public static void main(String[] args) {
        /*
         * 参数1:Calculator的类加载器(当初把Calculator加载进内存的类加载器)
         * 参数2:代理对象需要和目标对象实现相同接口Calculator
         * */
        Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
        //以Calculator实现类的Class对象作对比,看看代理Class是什么类型
        System.out.println(CalculatorImpl.class.getName());
        System.out.println(calculatorProxyClazz.getName());
        //打印代理Class对象的构造器
        Constructor<?>[] constructors = calculatorProxyClazz.getConstructors();
        System.out.println("----构造器----");
        printClassInfo(constructors);
        System.out.println("\n");
        //打印代理Class对象的方法
        Method[] methods = calculatorProxyClazz.getMethods();
        System.out.println("----方法----");
        printClassInfo(methods);
        System.out.println("\n");
    }

    public static void printClassInfo(Executable[] targets) {
        for (Executable target : targets) {
            // 构造器/方法名称
            String name = target.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            // 拼接左括号
            sBuilder.append('(');
            Class<?>[] clazzParams = target.getParameterTypes();
            // 拼接参数
            for (Class<?> clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(',');
            }
            //删除最后一个参数的逗号
            if (clazzParams.length != 0) {
                sBuilder.deleteCharAt(sBuilder.length() - 1);
            }
            //拼接右括号
            sBuilder.append(')');
            //打印 构造器/方法
            System.out.println(sBuilder.toString());
        }
    }
}

在这里插入图片描述
Proxy.getProxyClass()返回的Class对象是有构造器的!

开头说了,动态代理的使命有两个:
● 自动生成代理对象,让程序员免受编写代理类的痛苦
● 将增强代码与代理类(代理对象)解耦,从而达到代码复用

上面我们可以看到代理Class有一个构造器,需要传入InvocationHandler,虽然我们不知道这是啥,但可以试着传一下(JDK源码自带InvocationHandler):

public class ProxyTest {
    public static void main(String[] args) throws Exception {
        /*
         * 参数1:类加载器,随便给一个
         * 参数2:需要生成代理Class的接口,比如Calculator
         * */
        Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);

        // 得到唯一的有参构造 $Proxy(InvocationHandler h),和反射的Method有点像,可以理解为得到对应的构造器执行器
        Constructor<?> constructor = calculatorProxyClazz.getConstructor(InvocationHandler.class);

        // 用构造器执行器执行构造方法,得到代理对象。构造器需要InvocationHandler入参
        Calculator calculatorProxyImpl = (Calculator) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return 10086;
            }
        });

        // 看,有同名方法!
        System.out.println(calculatorProxyImpl.add(1, 2));
    }

}

但,InvocationHandler是干嘛的呢?从实验结果看,会发现每次调用代理对象的方法,最终都会调用InvocationHandler的invoke()方法:
在这里插入图片描述
怎么做到的呢

上面的代码中,根据代理Classd的构造器创建对象时,需要传入InvocationHandler。通过构造器传入一个对象引用,那么必然有个成员变量去接收它,没错,代理对象的内部确实有个成员变量invocationHandler,而且代理对象的每个方法内部都会调用handler.invoke()!也就是说,动态代理为了实现代理对象和增强代码的解耦,把增强代码也抽取出去了,让InvocationHandler作为它与目标对象的桥梁。

在这里插入图片描述
大致流程就是这样的:
在这里插入图片描述
JDK动态代理最终生成的Class,最终代理对象是proxy对象,而且实现了接口。

注意,静态代理的做法是把目标对象传入代理对象,而动态代理则把增强代码传入代理对象。那么,目标对象怎么办,这样一来虽然能执行增强代码,但执行不到目标方法了!

别慌!来看看invoke()方法的参数有哪些:
在这里插入图片描述
● Object proxy:很遗憾,是代理对象本身,而不是目标对象(不要调用,会无限递归,一般不会使用)
● Method method:方法执行器,用来执行方法(有点不好解释,Method只是一个执行器,传入目标对象就执行目标对象的方法)
● Obeject[] args:方法参数
在这里插入图片描述

至此,我们初步实现动态代理。

如何复用增强代码

上面这种方式,太low了,不忍直视…改进一下:

public class ProxyTest {
    public static void main(String[] args) throws Throwable {
        CalculatorImpl target = new CalculatorImpl();
        // 传入目标对象
        Calculator calculatorProxy = (Calculator) getProxy(target);
        calculatorProxy.add(1, 2);
    }

    /**
     * 传入目标对象,获取代理对象
     *
     * @param target
     * @return
     * @throws Exception
     */
    private static Object getProxy(final Object target) throws Exception {
        // 参数1:随便找个类加载器给它 参数2:需要代理的接口
        Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
        Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
        return constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName() + "方法开始执行...");
                Object result = method.invoke(target, args);
                System.out.println(result);
                System.out.println(method.getName() + "方法执行结束...");
                return result;
            }
        });
    }
}
解耦代理对象与增强代码

上面的代码还有问题:虽然传入任意对象我们都可以返回增强后的代理对象,但增强代码是写死的。如果我需要的增强不是打印日志而是其他操作呢?难道重新写一个getProxy()方法吗?所以,我们应该抽取InvocationHander,将增强代码和代理对象解耦(其实重写getProxy()和抽取InvocationHander本质相同,但后者细粒度小一些)。

public class ProxyTest2 {
        public static void main(String[] args) throws Exception {
            //获取目标类对象
            CalculatorImpl target = new CalculatorImpl();
            //传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)
            InvocationHandler handler =  getXxInvocationHandler(target);
            //获取代理类对象
            Calculator proxy = (Calculator)getProxy(handler);
            int subtract = proxy.subtract(2, 4);
            System.out.println(subtract);

        }

        private static InvocationHandler getXxInvocationHandler(Object target){
           return new InvocationHandler(){

               @Override
               public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                   System.out.println(method.getName()+"执行了");
                    Object  o = method.invoke(target,args);
                   System.out.println(method.getName()+"执行结束");
                   return o;
               }
           };
        }

        private static Object getProxy(InvocationHandler handler) throws Exception {
            Class<?> proxyClass = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
            Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
            Object o = constructor.newInstance(handler);
            return o;
        }
}
更好用的API Proxy.newProxyInstance()

目前为止,我们学习都是:Proxy.getProxyClass()

  1. 先获取proxyClazz
  2. 在根据proxyClazz获取构造器,需要传入InvocationHnandler
  3. 最后在生成代理对象,newInstance()
    JDK已经提供了一步到位的方法Proxy.newProxyInstance()
public class ProxyTest3 {
        public static void main(String[] args) throws Exception {
            //获取目标类对象
            CalculatorImpl target = new CalculatorImpl();
            //传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)
            InvocationHandler handler =  getXxInvocationHandler(target);
            //获取代理类对象
            Calculator o = (Calculator)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
            int subtract = o.subtract(3, 9);
            System.out.println(subtract);

        }

        private static InvocationHandler getXxInvocationHandler(Object target){
           return new InvocationHandler(){

               @Override
               public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                   System.out.println(method.getName()+"执行了");
                    Object  o = method.invoke(target,args);
                   System.out.println(method.getName()+"执行结束");
                   return o;
               }
           };
        }
}

完美

探究代理Class和代理对象

如果你只是想学会动态代理,上面的内容足够了。但我相信,对于JDK生成的代理Class对象和最终生成的代理对象,大家都有点云里雾里,想刨根问底。

所以我们来研究一下Proxy

Proxy.getProxyClass()会先克隆接口信息得到新的Class对象,然后进行后续的一系列处理。

在这里插入图片描述

小结

接口Class对象就好比是个太监,里面的方法和字段就是好像是他的一身武艺,但是他没有XDD(构造器),所以不能new实例,后继无人。
那怎么办呢?

正常途径(静态代理):
写一个类,实现该接口。这个就相当于大街上拉了一个人,认他做干爹。一身武艺传给他,只是比他干爹多了小DD,可以new实例。只要再传入目标对象,就能得到增强后的代理对象。

非正常途径(动态代理):
通过妙手圣医Proxy的克隆大法(Proxy.getProxyClass()),克隆一个Class,但是有小DD。所以这个克隆人Class可以创建实例,也就是代理对象。代理Class其实就是附有构造器的接口Class,一样的类结构信息,却能创建实例。

原本代理对象直接调用目标对象,现在是代理对象调InvocationHandler,InvocationHandler再调目标对象。Proxy代理对象内部有InvocationHandler对象,而InvocationHandler对象内部有我们塞进去的目标对象,所以最终通过代理对象可以调用到目标对象,并且得到了增强。所以,代理模式就是俄罗斯套娃…

从设计上理解JDK动态代理

山寨proxy

/**
 * 山寨Proxy类
 */
public static class MyProxy implements java.io.Serializable {

    protected MyInvocationHandler h;

    private MyProxy() {
    }

    protected MyProxy(MyInvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

    public static Object newProxyInstance(ClassLoader classLoader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h) throws Exception {
        // 拷贝一份接口Class(接口可能有多个,所以拷贝的Class也有多个)
        final Class<?>[] interfaceCls = interfaces.clone();
        // 这里简化处理,只取第一个
        Class<?> copyClazzOfInterface = interfaceCls[0];
        // 获取Proxy带InvocationHandler参数的那个有参构造器
        Constructor<?> constructor = copyClazzOfInterface.getConstructor(MyInvocationHandler.class);
        // 创建一个Proxy代理对象,并把InvocationHandler塞到代理对象内部,返回代理对象
        return constructor.newInstance(h);
    }

}

/**
 * 山寨InvocationHandler接口
 */
interface MyInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

目前上面的MyProxy有两个问题没解决:
● 返回的代理对象只是Proxy类型的,没法强转为目标接口类型
● 返回的代理对象即使能调用接口的同名方法,如何最终调用到它内部的InvocationHandler#invoke()呢

底层原理

首先,Proxy是JDK已经写好的类,一开始就没有实现Calculator接口,那么它的实例对象就不能强转为Calculator。那么java是如何解决这个问题的呢?方式很简单粗暴,因为JVM确确实实在运行时动态构造了代理类,并让代理类实现了接口,也就是我们经常看到的$Proxy0。
在这里插入图片描述

也就是说,我们通常理解的代理对象,并不是JDK Proxy的直接实例对象,而是JDK Proxy的子类 $ Proxy0的实例对象,而 $Proxy0 extends Proxy implements Calculator

当我们期望使用Proxy创建代理对象时,JDK会先动态生成一个代理类$Proxy0:

// 1.自动实现目标接口,所以代理对象可以转成Calculator
final class $Proxy0 extends Proxy implements InvocationHandlerTest.Calculator {
    private static Method m3;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        // 2.获取目标方法Method
        m3 = Class.forName("com.bravo.demo.InvocationHandlerTest$Calculator").getMethod("add", Integer.TYPE, Integer.TYPE);
    }

    public final int add(int n, int n2) {
        // 3.通过InvocationHandler执行方法,现在你能理解invoke()三个参数的含义了吗?
        //   this:就是$Proxy0的实例,所以是代理对象,不是目标对象
        return (Integer)this.h.invoke(this, m3, new Object[]{n, n2});
    }

}

最后一个问题是,代理对象 $proxy调用add()时,是如何最终调用到目标对象的add()方法的呢?观察上面的代码可以发现,代理对象的方法调用都是通过this.h.invoke()桥接过去的,而这个h就是InvocationHandler,在 $Proxy的父类Proxy中已经存在,而且会被赋值。
在这里插入图片描述

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值