动态代理实现流程的全新理解(包括invoke实现、proxy的定义)

本文是写给自己,搞清楚每个流程,参数怎么传递的,做提醒作用。

工具

说到动态代理,离不开的就是2个工具

  • Interface InvocationHandler
  • Class Proxy

一个是接口,一个是类

  • 定义:
  1. 真实对象->委托对象

  2. 动态代理->代理对象

  3. 接口->要实现功能

  • 肯定要实现以下步骤:
  1. 动态代理必须要实现委托对象的接口,并把接口义为属性
InvocationHandler

这是个接口,所以肯定得自己写个实现类来使用,例如:

public class MyInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(obj,args)
        return null;
    }
}

关键方法invoke,参数解释:

invoke(Object proxy, Method method, Object[] args) throws Throwable

1、proxy  动态代理调用方法时,传入的真实委托对象  //很多人这么解释吧,其实是错的。
2、method 动态代理调用方法时,被调用的那个方法
3、args   动态代理调用方法时,传入被调用方法的参数
4、method.invoke(obj,args) 在方法体里必须要实现的,在代理对象调用方法时使用  

上述方法参数,最重要的就是proxy,因为他被误用了,后面解释

Proxy

这个类在动态代理里面主要就是用到newProxyInstance,用来动态生成代理类,以下称为动态代理类:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

1、loader      代理的的类加载器
2、interfaces  委托对象的接口
3、h           在生成动态代理类时关联的MyInvocationHandler的实例化对象h
4、object      生成的动态代理类    

1、为什么要有loader,因为此时生成的objerct动态代理类没被加载到JVM,所以用类加载器,可以用MyInvocationHandler的实例化对象的类加载器

2、为什么要传interfaces 因为只有代理类也实现了同一个接口,才可以实现委托类的功能啊—__—

3、那为什么又要传入h呢,是因为在生成动态代理类时用到了h的invoke方法吗? 前排提示并没有

流程解析

好了,做了这么多铺垫,开始看动态代理如何实现,为了更为直观的展示每个步骤参数的作用,我代码耦合度非常低。。。

首先那必须是定义接口和实现类(委托对象)

public interface Phone {
    public void sell();
}
public class Apple implements Phone {
    @Override
    public void sell() {
        System.out.println("卖苹果手机");
    }
}
public class Huawei implements Phone {
    @Override
    public void sell() {
        System.out.println("卖华为手机");
    }
}

这里以卖手机的sell()方法为例

接着我们实现InvocationHandler接口,并把像开篇说的把接口定义为属性、实现有参构造

public class MyInvocationHandler implements InvocationHandler {

    private Phone target;

    public MyInvocationHandler(Phone phone) {
        this.target = phone;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("确认调用了invoke方法");
        method.invoke(target,args);
        return null;
    }
}

Q&A

  1. 为什么我们要先实现InvocationHandler而不是先proxy呢?

    因为proxy要关联InvocationHandler的实用类的实例化对象h。

  2. target是委托对象,那为什么要把它传入呢?是创建动态代理类时要用吗?

    因为method.invoke(target,args),这里的target用的上,和创建动态代理对象无关。

    \后面重点解释\

然后我们写一个测试方法,通过代理来卖苹果手机

public class Test {
    public static void main(String[] args) {
//        1、实例化对象
        Phone apple = new Apple();
//        2、实例化MyInvocationHandler,这里把apple作为委托对象
        MyInvocationHandler handleraApple = new MyInvocationHandler(apple);
//        3、生成代理对象
        /**
         * 1、这里因为我耦合度太低了,所以要改变代理对象得改2个参数,后附一个耦合度高一点的做法
         * 2、加载器用handler的,不多解释
         * 3、传入委托对象的接口,这是为了让代理类也实现相同接口,这样代理才能实现委托对象的功能啊。
         * 4、传入handler,每动态代理对象必须和一个handler关联,后面会用到
         * 5、为什么可以用Phone接收,因为代理对象不是也实现了Phone接口吗?
         */
        Phone proxy = (Phone) Proxy.newProxyInstance(handlerApple.getClass().getClassLoader(),apple.getClass().getInterfaces(),handlerApple);
//       4、实现代理功能
        proxy.sell();   
        //确认调用了invoke方法 
        //卖苹果手机
    }
}

结果与预期一致,输出:卖苹果手机。

但为什么会也输出:调用了MyInvocationHandler的invoke方法?我没有用handler.invoke操作啊

  • 第一,为什么会卖苹果手机:

是因为new MyInvocationHandler()传入了真实对象apple?

还是Proxy.newProxyInstance()中传入了真实对象apple的接口?

答案是:第一个,但不完全是。让我们来验证,我们把MyInvocationHandler参数改一下

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用了MyInvocationHandler的invoke方法");
    //  把target改为new Huawei()
        method.invoke(new Huawei(),args);   
        return null;
    }

再运行一次Test,结果是:

调用了MyInvocationHandler的invoke方法
卖华为手机

这就有意思了,我传入的一直是apple啊,只是方法体里面改为了new Huawei(),怎么会影响invoke传入参数?

public class Test {
    public static void main(String[] args) {
        Phone apple = new Apple();
        MyInvocationHandler handleraApple = new MyInvocationHandler(apple);
        Phone proxy = (Phone) Proxy.newProxyInstance(handlerApple.getClass().getClassLoader(),apple.getClass().getInterfaces(),handlerApple);
        proxy.sell();   
        //确认调用了invoke方法 
        //卖卖华为手机
    }
}

为了方便看,我把注释去掉,可以看到handlerApple接收的真实对象明明是apple,那么改方法体的参数,怎么会影响结果呢,通过debugger
在这里插入图片描述

可以看到proxy居然是null,不是说这是要传入的真实对象吗,那他怎么知道我要代理的真实对象是谁,应该调用哪个真实对象的方法?

这个就涉及到另外一个问题,这个invoke怎么会被调用,在哪一步被调用?

public class Test {
    public static void main(String[] args) {
        Phone apple = new Apple();
        MyInvocationHandler handlerApple = new MyInvocationHandler(apple);
        Phone proxy = (Phone) Proxy.newProxyInstance(handlerApple.getClass().getClassLoader(),apple.getClass().getInterfaces(),handlerApple);
        // proxy.sell();   
    }
}

当我把proxy.sell()注释掉,没有输出结果,这就证明,在生成动态代理对象时,invoke没有被调用,而是proxy调用方法时,自动跳转到handlerApple.invoke方法。

那么为什么会自动跳转,留个坑

同时再看另外一张截图,把proxy展开发现,他的居然属性还是APPLE!!
在这里插入图片描述

  • 这其实说明了,无论动态代理对象proxy关联的是哪个handlerApple,无论传入的代理proxy是谁,最终都用不上之前传入的委托对象。这个proxy更不是很多人说的真实委托对象,而是一个代理对象而已。

  • 在跳转到他的invoke方法时,他始终运行的是方法体中method.invoke(new Huawei(),args),第一个参数就是委托者啊,就是个反射而已。

好了,到这里应该很明白动态代理是怎么实现的了吧,一定要切记,委托对象一定要传入method.invoke(obj,args),这样不管你怎么改,一定可以实现真的的委托对象方法。所以大家都把他定义为传入的真实委托者。

这篇文章主要是我在CSDN找了很多动态代理的文章,都是抄来抄去,而且有错误,所以写了一个给自己看看。纠正了我在CSDN看到的问题:

1、很多文章说invoke是在newProxyInstance时调用了,其实没有。

2、invoke中的proxy也不是真实传入对象。

留了坑,怎么自动跳转的;这就和截图里面那个$Proxy0有关了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值