明白了一点反射+一点静态代理
网上看了几个动态代理的视频,感觉讲得都不是很清楚
实践出真知还是自己手搓了一些,结果就好多坑。
假如一个大老板想吃饭,但又觉得煮饭洗碗太麻烦了,就把煮饭希望这些事都扔给代理类【仆人】处理,自己实现吃的功能即可
按照上图分别定义三个类
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接口的类,都可以用此方法代理了。
=============================分割线==================================
上面都只说了怎么实现动态代理,没有提及实现原理。
下面来推敲一下实现原理
🤣原理我还没弄懂,看官们想看的再等等,弄懂了再来吹吹