【JavaWeb笔记】静态代理、动态代理还有class jdk.proxy1.$Proxy0 cannot be cast to class……

明白了一点反射+一点静态代理

网上看了几个动态代理的视频,感觉讲得都不是很清楚

实践出真知还是自己手搓了一些,结果就好多坑。

假如一个大老板想吃饭,但又觉得煮饭洗碗太麻烦了,就把煮饭希望这些事都扔给代理类【仆人】处理,自己实现吃的功能即可

按照上图分别定义三个类

public interface Eat {
    // Eat接口类
    void eat();
}
public class Master implements Eat{

    @Override
    public void eat(){
        System.out.println("主人:干饭!");
    }
}

这里一定要把Eat写成一个接口类!因为动态代理只能基于接口!!!

如果按照静态代理的写法,【仆人(Servant)】类可以简单写成

public class StaticServant implements Eat{
    Master targetMaster; // 代表被代理的对象,本例即仆人伺候的主人
    StaticServant(Master targetObj){
        this.targetMaster = targetObj; // 仆人必须有个伺候的主人吧,所以构造时传个主人
    }

    // 这里就是代理的主体部分了,仆人帮主人煮饭洗碗...
    @Override
    public void eat(){
        System.out.println("仆人:买菜煮饭……");
        this.targetMaster.eat();
        System.out.println("仆人:收拾饭桌洗碗……");
    }
}

此时再main方法中让仆人代理主人,再让仆人代理主人执行一下eat方法

public static void main(String[] args) {
        Master m1 = new Master();
        StaticServant ss1 = new StaticServant(m1);
        ss1.eat();
}

就可以得到想要的输出了

不过这种方式,假如仆人想给【公司老总类】洗碗做饭,就又得给自己加个【公司老总类】的成员变量,假如他又想给【富二代少爷类】洗碗做饭,就又得给自己加个【富二代少爷类】的成员变量……

这样肯定是不行的,必须有什么不这么笨的方法

于是动态代理应运而生

不管被代理类具体是什么类,只要这个类实现了Eat接口,那么动态代理就都能以相同方式给它代理了。(为什么是“只要这个类实现了Eat接口”呢,因为动态代理是基于接口的!!!)

实现动态代理的话,接口和被代理类(主人类)都不需要动,只需要修改一下代理。

public class DynamicServant implements InvocationHandler {
    // 被代理的对象(这里就不是Master了,因为要换成富二代少爷也能代理……)
    Object targetObj; 

    DynamicServant(Object targetObj){
        // 这个构造函数不用多讲了吧
        this.targetObj = targetObj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 仆人在主人吃饭前做的动作放这
        System.out.println("仆人:买菜煮饭……"); 
        // 这是被代理类要做的事(即吃饭)
        Object invoke = method.invoke(this.targetObj, args);
        // 仆人在主人吃饭后做的动作
        System.out.println("仆人:收拾饭桌洗碗……");
        return invoke;
    }
}

你没猜错,这里的invoke就是来实现代理功能的。(那要怎么调用这个代理呢?这个先不急后面再讲)

上面invoke方法,除了仆人要做的两件事(吃饭前和吃饭后做的事)你可以按照自己需求去更改,其它地方都可以直接照抄(假如你不想懂的话……然而其实懂了也是照抄),包括invoke这个方法名,和里面的所有参数。

接着讲怎么调用这个代理呢。

在main函数中,按照原先的逻辑,应该是先new一个master,然后再new一个servant,然后servant调用eat方法,结束!

于是我们写下如下代码(这个代码有坑,大家别先急着运行)。

public static void main(String[] args) {
    // 首先new一个master
    Master m1 = new Master();
    // 接着new一个servant
    DynamicServant s1 = (DynamicServant) Proxy.newProxyInstance(
        m1.getClass().getClassLoader(), 
        m1.getClass().getInterfaces(), 
        new DynamicServant(m1)
    );
    // 最后用new出来的servant调用eat方法
    s1.eat();
}

这里的servant的创建方法有点奇怪,因为是动态代理。

我们需要用Proxy.newProxyInstance来创建一个servant实例,

第一个参数填m1(即被代理的对象),后面的.getClass().getClassLoader()直接照抄(如果看方法名,可以知道这是要获得m1的类加载器),

第二个参数同理,m1后面的直接照抄(同理可知此参数要的是m1的接口——在这里是Eat接口),

第三个参数类型要new一个servant。(看第三个参数的形参名是InvocationHandler,是不是很眼熟,就是DynamicServant实现的那个接口(看DynamicServant代码的第一行),所以第三个参数要填DynamicServant的一个实例)

因为Proxy.newProxyInstance返回的是Object类型,需要手动强转一下变成想要的Servant。

最后用生成的servant调用eat方法。

然后成功发现,报错了。

我们发现s1里根本没有eat方法。

先退一步,删除s1.eat()这一行。

编译器没有报错,但是一运行还是出现了报错信息

Exception in thread "main" java.lang.ClassCastException: class jdk.proxy1.$Proxy0 cannot be cast to class com.test.proxyTest.DynamicServant (jdk.proxy1.$Proxy0 is in module jdk.proxy1 of loader 'app'; com.test.proxyTest.DynamicServant is in unnamed module of loader 'app')
	at com.test.proxyTest.Master.main(Master.java:13)

这就是为什么我前面强调了两次“动态代理只能基于接口!!!”(参考动态代理之: com.sun.proxy.$Proxy0 cannot be cast to 问题_com.sun.proxy。$proxy0 cannot be cast to_codingCoge的博客-CSDN博客

可能以静态代理的思维我们确实是要“创建静态代理类、使用静态代理类实例调用eat方法”,然而在动态代理中,Proxy.newProxyInstance返回的不是代理类实例(仆人),而是返回 【被代理的接口】(Eat实例),在本例中,Proxy.newProxyInstance返回的实例应该用Eat来接收。即

public static void main(String[] args) {
    // 首先new一个master
    Master m1 = new Master();
    // 接着new一个Eat
    Eat e1 = (Eat) Proxy.newProxyInstance(
        m1.getClass().getClassLoader(), 
        m1.getClass().getInterfaces(), 
        new DynamicServant(m1)
    );
    // 最后用new出来的Eat调用eat方法
    e1.eat();
}

运行!

终于得到想要的结果

而利用动态代理,无论吃饭的是公司老总还是老总他儿子,只要是实现了Eat接口的类,都可以用此方法代理了。

=============================分割线==================================

上面都只说了怎么实现动态代理,没有提及实现原理。

下面来推敲一下实现原理

🤣原理我还没弄懂,看官们想看的再等等,弄懂了再来吹吹

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值