[jvm解析系列][十四]动态代理和装饰模式,带你看源码深入理解装饰模式和动态代理的区别。

Posted 胖子程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[jvm解析系列][十四]动态代理和装饰模式,带你看源码深入理解装饰模式和动态代理的区别。相关的知识,希望对你有一定的参考价值。

不知道大家知不知道设计模式中有一种叫做装饰,举一个简单的例子。

一天一个年轻领导小王讲话:咳咳,我们一定要xxx抓紧xxxx学习xxx的精神!好,今天的会议结束!

然后有一个老领导李同志接过来说:那个我在补充两点,个别同志xxx,一定要注意xxx。好散会。

然后另一天小王同志又在讲话:xxx两手都要抓,xxxx一定要注意。

这个时候老周同志出来了:嗯,小王讲的很好,我还有几点要补充xxxx。




那么很明显,小王同志的讲话方法不是很让人满意,那么老李领导或者老周领导可以接过来继续装修一下。其实这就是装饰模式。我们画张图来理一下关系。


我们根据图来写一下代码

public class DecoratorDemo {
	public static void main(String[] args){
		new 老李(new 小王()).讲话();
	}
}
interface 开会{
	public void 讲话();
};
class 小王 implements 开会{
	public void 讲话(){
		System.out.println("小王同志在讲话");
	};
}
class 补充讲话{
	开会	meeting;
	public 补充讲话(开会	meeting){
		this.meeting = meeting;
	}
	public void 讲话(){
		meeting.讲话();
	}
	
}
class 老李 extends 补充讲话{
	public 老李(开会 meeting) {
		super(meeting);
		// TODO Auto-generated constructor stub
	}

	public void 讲话(){
		super.讲话();
		System.out.println("老李同志补充讲话");
	};
}
class 老周 extends 补充讲话{
	public 老周(开会 meeting) {
		super(meeting);
		// TODO Auto-generated constructor stub
	}

	public void 讲话(){
		super.讲话();
		System.out.println("老周同志补充讲话");
	};
}
在调用老李补充的时候就会输出:

小王同志在讲话
老李同志补充讲话
但是这种方法虽然做到了补充功能但是整个过程都是静态编译好的。在运行时期也很难修改。





我们接下来看一看动态代理
同样的例子,我们来实现一下代码。

public class DynamicProxyDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		开会 meeting = (开会) new 代理().bind(new 小王());
		meeting.讲话();
	}
	
}
interface 开会{
	public void 讲话();
}
class 小王 implements 开会{
	public void 讲话(){
		System.out.println("小王同志在讲话");
	};
}
class 代理 implements InvocationHandler{
	Object obj ;
	public Object bind(Object obj) {
		this.obj = obj;
		return  Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("我要补充两句。。。");
		return method.invoke(obj, args);
	}
	
}
输出:

我要补充两句。。。
小王同志在讲话
显而易见的是动态代理实现了动态的编程,在原始类和接口都没有知道的时候就可以确定代理类需要做什么。比如说我们把小王讲话换成小张跳舞。其他的都不用变,最后结果还是一样的。

代码

public class DynamicProxyDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		舞厅 meeting = (舞厅) new 代理().bind(new 小张());
		meeting.跳舞();
	}
	
}
interface 舞厅{
	public void 跳舞();
}
class 小张 implements 舞厅{
	public void 跳舞(){
		System.out.println("小张同志在跳舞");
	};
}
class 代理 implements InvocationHandler{
	Object obj ;
	public Object bind(Object obj) {
		this.obj = obj;
		return  Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("我要补充两句。。。");
		return method.invoke(obj, args);
	}
	
}
输出:

<p class="p1">我要补充两句。。。</p><p class="p1">小张同志在跳舞</p>
可以看出来这位老领导兴致勃勃,怎么都要讲两句,也就是说装饰模式真的是在静态的状态上补充和加强原来的代码。但是动态代理更像是一个恶霸,不管你干什么只要过来我这一定要输出我想输出的东西。





那么他是怎么实现的呢?

看我们动态代理的源码。很有意思我们唯一疑惑的地方应该是他:

return  Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
他到底返回了什么?我们跟进去源码看看:

我把几个比较重要的步骤截取下来:

final Class<?>[] intfs = interfaces.clone();//得到接口
Class<?> cl = getProxyClass0(loader, intfs);//生成一个代理类
 final Constructor<?> cons = cl.getConstructor(constructorParams);//得到代理类构造器
return cons.newInstance(new Object[]{h});//返回一个代理类实例

我们可以通过上面的代码看出来,他根据我们传入的类的信息生成了一个代理类。其实在这个过程中他根据需要的接口信息生成了一个类然后写入了代理的方法,在每一个方法里面都使用了invoke的调用,最后通过这一句话调用到了我们真正想要修饰的方法。

	return method.invoke(obj, args);


(由于我的电脑最近新装的系统,没有反编译装置,没有办法看输出的类,所以通过文字叙述和图片描述)
图片:




以上是关于[jvm解析系列][十四]动态代理和装饰模式,带你看源码深入理解装饰模式和动态代理的区别。的主要内容,如果未能解决你的问题,请参考以下文章

[jvm解析系列][十四]动态代理和装饰模式,带你看源码深入理解装饰模式和动态代理的区别。

[jvm解析系列][十二]分派,重载和重写,查看字节码带你深入了解分派的过程。

[jvm解析系列][十二]分派,重载和重写,查看字节码带你深入了解分派的过程。

带你认识4种设计模式:代理模式装饰模式外观模式和享元模式

代理模式、装饰者模式

代理模式和装饰者模式的区别