网上很多相关的例子了,讲来讲去,都没讲到原理的东西
这里我把动态生成的类拿出来拆解着讲,看能不能说明白,希望看到我写的朋友都不白忙。
下面重点是我带着大家读生成的源码的部分,如果你也想读源码,不会读,那么跟着我这篇文章,简单复制出来里面两段代码,运行一下,就能探究动态代理到底是什么
我们在使用类的功能时候,不能修改类源码,怎么办?
1、继承,那么慎用继承,我们还有可以用代理
2、代理又有很多组合? 我们可以结合组合模式使用
3、组合很多,方法功能还固定,我们用jdk动态代理解决
下面我通过图片和文字的形式,慢慢深入上面三个点到jdk代理的演变过程
定义一个人类 实现了可移动的接口Movable
/**
* Created by lijinquan on 2020/8/30.
*/
public class Person implements Movable {
@Override
public void move() {
System.out.println("上班写代码");
try {
Thread.sleep(12*3600000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Movable接口
/**
* Created by lijinquan on 2020/8/30.
*/
public interface Movable {
void move();
}
这里我想要记录人类上班这个功能扩展,如果修改源码那么以后加入更多功能会一直修改这个人类,我们可以通过一个代理类来扩展这个功能
public class PersonWorkTimeProxy implements Movable {
Person person;
public PersonWorkTimeProxy(){}
public PersonWorkTimeProxy(Person person){
this.person=person;
}
@Override
public void move() {
long shangban = System.currentTimeMillis();
persion.move();
long xiaban = System.currentTimeMillis();
// System.out.println("今天工作时长:"+(xiaban-shangban)+" 小时");
System.out.println("今天工作时长:"+8 +"小时");
}
}
接着 , 比如我们又要记录人类早上的一些行为事件也是写一个代理类
public class PersonMornignProxy implements Movable {
Movable movable;
public PersonMornignProxy(){}
public PersonMornignProxy(Movable movable){
this.movable=movable;
}
@Override
public void move() {
System.out.println("刷牙!");
System.out.println("洗脸!");
movable.move();
}
}
上面的类关系我们说清楚并且写出来了,下面可以对其进行测试
public class Main {
public static void main(String[] args) {
Person person = new Person();
PersonWorkTimeProxy personWorkTimeProxy = new PersonWorkTimeProxy(person);
PersonMornignProxy personMornignProxy = new PersonMornignProxy(personWorkTimeProxy);
personMornignProxy.move();
}
}
上面是一个比较固定的静态代理
比如说上面是实现了Movable这个可移动的方法,再用早上/上班时间的记录代理,那么人类那么多功能,每天干那么多事,吃饭,睡觉,思考,五官,四肢,等属性,或者行为如果通过以上代理去扩展,必定需要写大量的类和大量的扩展代码,而且必须要知道每个类里面有那些方法,我们怎么改良呢?
动态代理(全能代理)
1.jdk的动态代理
Persion依然实现Movable , Movable还是那个Movable
public class Person implements Movable {
@Override
public void move() {
System.out.println("正在写代码!");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public interface Movable {
void move();
}
再来一段测试代码
public class Main {
public static void main(String[] args) {
Person person = new Person();
Movable movable =(Movable) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Movable.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("methor" + method.getName() + "start.......");
Object invoke = method.invoke(person, args);
System.out.println("methor" + method.getName() + " end !");
return invoke;
}
});
movable.move();
}
}
Proxy.newProxyInstance :固定写法,jdk自己的,从名称我们可以看出来是new一个代理实例,其中里面的参数:
1、loader就是被代理的对象:Person.class.getClassLoader()
2、interfaces是一个接口数组,因为一个类可以实现多个接口:new Class[]{Movable.class}
3、new InvocationHandler() --翻译一下Inocation调用的意思,Handler处理的意思. 总的意思是被代理对象的那个方法被调用了我们该怎么处理,其实它是一个接口
里面只有一个方法
需要我们自己实现
既然如此那我们抽出上面这段代码:下图红色部分
得到如下所示
public class Main {
public static void main(String[] args) {
Person person = new Person();
Movable movable = (Movable) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Movable.class}, new WorkTimePersionInvocationHandler(person));
movable.move();
}
}
class WorkTimePersionInvocationHandler implements InvocationHandler {
Person person;
public WorkTimePersionInvocationHandler(){}
public WorkTimePersionInvocationHandler(Person person){
this.person=person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("methor" + method.getName() + "start.......");
Object invoke = method.invoke(person, args);
System.out.println("methor" + method.getName() + " end !");
return invoke;
}
}
运行一下:
这里要注意输出的结果:
methormovestart.......
methormove end !
这两段输出,来自于
我们自始至终都没有调用到invoke方法,我们只有movable.move();理论上说我们指调用了人类的move()
我在网上找了一段代码;有了它,能方便我们查看动态生成的代码
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
我们把他放入到测试方法中
public class Main {
public static void main(String[] args) {
Person person = new Person();
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
// System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
Movable movable = (Movable) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Movable.class}, new WorkTimePersionInvocationHandler(person));
movable.move();
}
}
运行结束后在项目结构中多了一个com文件夹
展开这个包
这个
$Proxy0.class我们通过Idea反编译出来的,你说看到的源码
我们研究一下这个源码
$Proxy0继承了Proxy实现了Movable ,当然能找到实现了的Movable的move方法
看到了
super.h.invoke(this, m3, (Object[])null);这段代码
首先进入supper,就是Proxy这个父类
我们看到h是
InvocationHandler h
那InvocationHandler被我们的
WorkTimePersionInvocationHandler
下面这图记得吧
哦是不是一下就明白了
super.h.invoke(this, m3, (Object[])null);这段代码
这段代码的invoke其实是调用了下面这个方法
所以我们上面虽然是movable.move()这么一个调用,其实内部进行了这么些操作,疑惑一下子释然了吧
我们撸一撸流程
首先这个代理生成了一个类赋值给了movable(也就是我们上面反编译出来的$Proxy0赋值给了movable)
Movable movable = (Movable) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Movable.class}, new WorkTimePersionInvocationHandler(person));
然后这个类调用了
movable.move();
move()中又
super.h.invoke(this, m3, (Object[])null);
而这个h就是我们自己实现的
WorkTimePersionInvocationHandler
里面的实现方法的东西比较简单
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("methor" + method.getName() + "start.......");
Object invoke = method.invoke(person, args);
System.out.println("methor" + method.getName() + " end !");
return invoke;
}
继续拆解我们实现的接口方法(参考以上完整方法)
public Object invoke(Object proxy, Method method, Object[] args)
中的参数
1、proxy:上面我们反编译出来的类中的
super.h.invoke(this, m3, (Object[])null);
可以看出,proxy就是指this,这个this就是动态代理生成的一个类自己本身($Proxy0就动态生成的代理类)而不是被代理对象
2、method:这个是一个方法对象,它本身也是个方法,我们学习过反射知道getClass.getMethods()可以获得一个方法数组,而在这里的方法对象得到不同的方法:我们看生成的代理对象中有一段静态代码块
super.h.invoke(this, m3, (Object[])null);
中的m3参数就是这个代码块中的m3
而这些初始化的m3都是这个$Proxy01的未被初始化的全局变量
3、args:这个就是我们要调用方法时候,可能需要传参数,这里为空所以它是这样的(Object[])null一个对象数组
以上
我们可以method.getName()得到被调用方法的名称
Object invoke = method.invoke(person, args);
这句可以知道传了Person的引用,那么它就是调用了persion.move()
那第一个参数也没用上啊,第一个参数是代理生成类,里面有hashCode和toString得方法,我们有极大可能需要用到
所以,最终得到以上的输出
一张图总结
一起看看这个动态代理类是怎样生成的,Idea 下Ctrl+Alt鼠标点击这个方法
进来后,非空校验和安全检查后,我们看到一段代码,注意我红色方框位置,
翻译过来的意思就是寻找或者生成我们设计的代理类
跟进去
方法头部的注释说的是:生成代理类。 必须调用checkProxyAccess方法,在调用此权限之前执行权限检查。
方法内部的注释说的是 :
//如果给定加载器定义的代理类实现
//给定的接口存在,这只会返回缓存的副本;
//否则,它将通过ProxyClassFactory创建代理类
注释说得很明白,这个方法就是生成代理类的,这个代理类从缓存读取,如果有直接返回,没有就创建一个
具体怎么生成代理类Class的呢?继续跟进去
接着看
上面这部分都是校验
往下翻 ,找到了这个
Generate the specified proxy class.
跟进去
找到这行 ,这个生成Class最终返回也是var4
final byte[] var4 = var3.generateClassFile();
跟进去,真相都在这里了,它通过addProxyMethode()将这个class文件中的方法装进去,method.add构造也装进去
那我们的代理方法谁生成的呢?继续往下看
再进去
注意:这一步它其实还有一步实现类,看下面这个
到这里我们还特地百度了下asm干什么的,原来这个东西可以操作二进制文件
应用最为广泛的操作二进制文件的
如有进一步学习的兴趣可以看看我查到觉得比较好读懂的一篇转载文章,这里给上链接https://www.jianshu.com/p/a1e6b3abd789
知道代理的class怎么获得后,我们看它获得Class之后的操作
继续跟进去
里面注释的意思:权限检查调用方是否在其他运行时包中代理类
也就是说这里做了个权限校验
好继续
这个看名称和返回类型就知道是构造有关,跟进去
翻译:
返回一个{@code构造函数}对象,该对象反映此{@code Class}对象表示的类的指定公共构造函数。 {@code parameterTypes}参数是{@code Class}对象的数组,这些对象按声明的顺序标识构造函数的形式参数类型。
如果此{@code Class}对象表示在非静态上下文中声明的内部类,则形式参数类型包括显式的封闭实例作为第一个参数。
要反映的构造函数是此{@code Class}对象表示的类的公共构造函数,该对象的形式参数类型与{@code parameterTypes}指定的形式参数类型匹配。
从上面的参数我们不难看出来,Jdk动态代理必须要被代理类实现接口,要不然,因为它通过接口去指定代理类去生成接口
只有给定接口它在生成的代理类里面才知道需要生成哪些方法
这是它自身的不足