专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java 设计模式 之 代理模式 (1)(java设计模式之代理模式)

temp10 2024-10-24 17:23:40 java教程 21 ℃ 0 评论

前言 : 最近正在学习一些设计模式 ,正好看到代理的这一块,写这篇文章的目的就是让自己这个菜鸟加深点印象,如有出错的地方还请海涵 !

Java 设计模式 之 代理模式 (1)(java设计模式之代理模式)


代理模式的概念 :


为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用


看着概念,大家可能不太清楚,那么我们一步一步的通过举例来说明,


具体的我们分为 3 个步骤来 引出 我们的终极BOSS,JDK动态代理 Proxy


  • 1. 无代理
  • 2. 静态代理
  • 3. 动态代理


我们 以人类需要吃饭的例子来说明吧


一 、无代理


//定义人这个接口
public interface Person {
	//定义人这个吃饭的行为
	void eat();
}

// 这个人接口的实现类
public class PersonImpl implements Person{
	@Override
	public void eat() {
		//这里我们吃点面
		System.out.println(" ======吃面====== ");
	}
}



当我们需要在吃面之前后做一些事,比如,选择餐厅 ,霸王餐之类的 ... ,那么我们正常的会这样做


	@Override
	public void eat() {
		before();
		//这里我们吃点面
		System.out.println(" ======吃面====== ");
		after();
	}
	
	public void before(){
		//这里吃之前做的事
		System.out.println("=======选择目标餐厅======");
	} 
	
	public void after(){
		//这里吃之后做的事
		System.out.println("======假装上厕所从后门溜走=======");
	} 



这样写是不是很简单, 大多时候我们就是这样写的,但是这样写就使得这块没有扩展性和灵活性了,


因为,这里是吃面,那当我们吃大米饭的时候也需要这样做的时候,是不是又要写一个实现类来实现Person接口,然后在里面写上


 before() , eat() , after() 



甚至,当我们,eat() 之后 突然良心发现,想给钱了(目前是吃霸王餐),那还是不是又在里面加代码,


	public void after(){
		//这里吃之后做的事
		System.out.println("======良心发现->付账=======");
	} 



然后没处都修改过去 ... ,然而, 当这些代码都在别人的 原来的代码上改的, 甚至在jar里面的呢,


是不是 头都大了


PS : 头都大了(TDDL),貌似是阿里开发的一个 支持数据库读写分离分表分库等 强大功能的 集成组件


那么 我们来看看静态代理模式是怎么实现的吧


二、静态代理


public class PersonProxy implements Person {
	Person person ;
	public PersonProxy() {
		person = new PersonImpl() ;
	}

	@Override
	public void eat() {
		before();
		person.eat();
		after();
	}
	
	public void before(){
		//这里吃之前做的事
		System.out.println("=======选择目标餐厅======");
	} 
	
	public void after(){
		//这里吃之后做的事
		System.out.println("======假装上厕所从后门溜走=======");
	} 
}

// 这个人接口的实现类
public class PersonImpl implements Person{

	@Override
	public void eat() {
		//这里我们吃点面
		System.out.println(" ======吃面====== ");
	}
}



通过代码可以看出,


  1. 我们新建了一个PersonProxy 类,
  2. 实现了和 PersonImpl 同一个的 接口 Person
  3. 我们在 他的内部定义了 Person这个属性, 却在构造函数中 将他指向了PersonImpl ;
  4. 重写了 父类的 eat方法,在这个eat() 方法中调用 具体实现类的
  5. 并且在这个方法中的前后调用 before(), after()


下面我来看一下这样写是怎么调用的


	public static void main(String[] args) {
		
		Person PersonProxy = new PersonProxy();
		
		PersonProxy.eat();
	}



这里 我们还是持有 Person 对象的应用,只是实例化的过程变了,执行这个过程,会为我们自动加上前后的是;试想一下,我们这里的PersonProxy 对象中的 构造函数中的实例化类型是动态传入的, 是不是能解决,去吃大米饭的问题了,并且做一下和吃面前后一样要做的是 我们再来看看,怎么实现


首先定义吃大米饭的实现类 PersonAImpl(注意这里名称多了一个A)


	class PersonAImpl implements Person{
		@Override
		public void eat() {
			System.out.println(" ======吃大米饭====== ");
		}
	}



看我门稍稍修改之后的类 PersonProxy 类


public class PersonProxy implements Person {
	Person person ;
	public PersonProxy(Person person) {
		this.person = person ;
	}

	@Override
	public void eat() {
		before();
		person.eat();
		after();
	}
	
	public void before(){
		//这里吃之前做的事
		System.out.println("=======选择目标餐厅======");
	} 
	
	public void after(){
		//这里吃之后做的事
		System.out.println("======假装上厕所从后门溜走=======");
	} 
	
	public static void main(String[] args) {
		Person PersonProxy = new PersonProxy(new PersonAImpl());
		PersonProxy.eat();
	}
	
}



注意上面的构造函数,仅仅改动那一块地方,需要代理的对象右开发者手动传入,


再看看我们 在调用处的函数 :


	public static void main(String[] args) {
		Person PersonProxy1 = new PersonProxy(new PersonImpl());
		PersonProxy1.eat();
		System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
		System.out.println("+++++++++++++++我是可爱的分割线+++++++++++++++++++++++++++++++++");
		System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
		Person PersonProxy2 = new PersonProxy(new PersonAImpl());
		PersonProxy2.eat();
	}



再看看输出结果 :


=======选择目标餐厅======
 ======吃面====== 
======假装上厕所从后门溜走=======
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++我是可爱的分割线+++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
=======选择目标餐厅======
 ======吃大米饭====== 
======假装上厕所从后门溜走=======



总结 一下吧:


我们这样写是不是 不需要在意你吃什么了,


就算你要 吃翔,也可以先悄悄的选择场所(PS:听说现在某些旅游景点的厕所很高打上 ...,有大屏电视还是有精美的装修,还有 ...各种其他的,就像个休息室一样的。真是贫穷限制了我的想像 。),然后悄悄的溜走了;


当然 .. ... .. ^_* 。


优点 :


1. 是我们不需要去改eat() 函数里面的 各种源代码,做各种判断;


2. 要是还有别的吃法,需要这样的流程,我们只需要写个实现Person 这个接口的实现类就好了


3. 如果以后这里改为想付账了,我只需要改个PersonProxy 这个代理类就OK;


缺点:


当我们的吃饭不是吃饭这个动作的时候, 并且我也要做那ea() 前后 的那些事,


比如去上班,别问我问什么上班前需要找餐厅 。。。


那么我们的实现方式有可能是 :


  1. 新写一个接口, 工作的的接口, 并且写上这个工作的实现类, 然后在写一个代理类,说白了就是复制上面的一套,并把,eat() 改变为 work() , 然后就 over ;
  2. 在原来的接口接触上增加work() 方法,并在代理中重写work() 并且在里面写上before() ,after()


大家来看看会存在什么样的问题:


  • 两者都存在大量的重复代码 , 当然第二种会优于第一种,
  • 第二种的,当新加的和原来的 的业务都完全没关系时,能定义中同一个就接口中吗?


下面我们来看看第三种解决方案、


三、动态代理


其实我们在上面的 第二种方案中已经用到了一点动态的感觉了,比如,那个对象是有 具体传入的对象类型来控制的,充分的利用了java的多态,


那么我们这里说的动态,是java 在运行是产生一个新的class , 和上面的多态完全是不同的;


下面我们来看看 具体的实现代码吧:


原来的类:


// 接口 
//定义人这个接口
public interface Person {
	//定义人这个吃饭的行为
	void eat();
}

// 这个人接口的实现类
public class PersonImpl implements Person{

	@Override
	public void eat() {
		//这里我们吃点面
		System.out.println(" ======吃面====== ");
	}
}
class PersonAImpl implements Person{
	@Override
	public void eat() {
		System.out.println(" ======吃大米饭====== ");
	}
}



新的:


package noproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class PersonProxyHandle implements InvocationHandler {

	private Object obj ;   //  接收 任何 的对象,没有任何的限制
	
	//绑定委托对象(即真正的目标对象) 并返回代理对象
	public Object bind(Object tar) {
		this.obj = tar;
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object res = null ;
		before();  //  之前 
		res = method.invoke(obj, args);
		after();  //  之后
		return res;
	}
	
	public void before(){
		//这里吃之前做的事
		System.out.println("=======选择目标餐厅======");
	} 
	
	public void after(){
		//这里吃之后做的事
		System.out.println("======假装上厕所从后门溜走=======");
	} 

}



代码是不是很简单 ,就短短的几行,


我们先来看看调用处是怎么写的 :


	public static void main(String[] args) {
		PersonProxyHandle pph1 = new PersonProxyHandle();
		Person person1 =  (Person) pph1.bind(new PersonImpl());
		person1.eat();
		System.out.println("<<<<<<<<<<<<<<<<<<<<<我是华丽的分割线<<<<<<<<<<<<<<<<<<<<<<<<<<");
		PersonProxyHandle pph2 = new PersonProxyHandle();
		Person person2 =  (Person) pph2.bind(new PersonAImpl());
		person2.eat();
	}



再看看输出结果


=======选择目标餐厅======
 ======吃面====== 
======假装上厕所从后门溜走=======
<<<<<<<<<<<<<<<<<<<<<我是华丽的分割线<<<<<<<<<<<<<<<<<<<<<<<<<<
=======选择目标餐厅======
 ======吃大米饭====== 
======假装上厕所从后门溜走=======



再翻看前面静态代理的结果,是不是一模一样,


我们 再来看看 扩展性 :


扩展(一):


新建一个接口 AAA,再一个类 AAAImpl, 记住这类 和前面的业务逻辑完全不一样 ;可以自己天马行空的发挥想象


public interface AAA {
	void doing();
}

public class AAAImpl  implements AAA{
	@Override
	public void doing() {
		System.out.println("我这里可能需要做些什么,比如泡妞,玩游戏打球");
	}
}



我们再看看调用处是怎么写的:


	public static void main(String[] args) {
		PersonProxyHandle pph2 = new PersonProxyHandle();
		AAA aaa =  (AAA) pph2.bind(new AAAImpl());
		aaa.doing();
	}



输出结果 :


=======选择目标餐厅======
我这里可能需要做些什么,比如泡妞,玩游戏打球
======假装上厕所从后门溜走=======



扩展(二):


我们在原来的接走新增方法:


AAA 接口中新增方法 sleep()


public interface AAA {
	void doing();
	
	void sleep();
}



然后在AAAImpl 中重写它


public class AAAImpl  implements AAA{
	@Override
	public void doing() {
		System.out.println("我这里可能需要做些什么,比如泡妞,玩游戏打球");
	}

	@Override
	public void sleep() {
		System.out.println("我们玩累了,需要休息了, 我要取宾馆睡觉或者回家睡觉");
	}
}



我们再来看调用及输出:


//这里是调用的代码
public static void main(String[] args) {
	PersonProxyHandle pph2 = new PersonProxyHandle();
	AAA aaa =  (AAA) pph2.bind(new AAAImpl());
	aaa.sleep();;
}

//以下是输出的内容 :

=======选择目标餐厅======
我们玩累了,需要休息了, 我要取宾馆睡觉或者回家睡觉
======假装上厕所从后门溜走=======



看到没有,我们这里只改变了 调用函数的地方是不是很神奇;


我们不需要在自己写代理类(原 PersonProxy 类)以及不需要自己在每个原代理类中的方法中调用 before() 和after()


现在我们来看看 具体是怎么实现的:


核心类 PersonProxyHandle , 中核心的方法


	private Object obj ;   //  接收 任何 的对象,没有任何的限制
	
	//绑定委托对象(即真正的目标对象) 并返回代理对象
	public Object bind(Object tar) {
		this.obj = tar;
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), 
                            obj.getClass().getInterfaces(), this);
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object res = null ;
		before();  //  之前 
		res = method.invoke(obj, args);
		after();  //  之后
		return res;
	}



里面唯一个 属性, Object obj ;


这里属性代表这这里可以接受任何的对象,并且帮他产生一个代理的对象(就像原来的PersonProxy)


就是bind 返回的返回值,这个返回值不是一个AAAImpl 类的具体实例,而是一个和AAAImpl一样实现了AAA的一个类,就像我们在静态代理中自己手动实现的那个类PersonProxy), 所以这里也还是用了 AAA aaa 这个来接收;我们这里可以过开发工具打断点看出,


看下面的图,很重要 ,敲黑板 !!!



aaa 变量的应用是 $Proxy0 ; 这个类继承了 Proxy 实现了 AAA 接口 ;


并且里面 有 重写 eq , hashCode ,toString 以及 AAA 的方法(sleep() )


看下这个 $Proxy0 这个反编译后的大致代码, 主要就是 sleep() ;


 private static Method m3;   

 ... //  eq hashCode toString

 public final String sleep()  
  {  
     return (Object)this.h.invoke(this, m3, null); 
  }

static  
  {   
    ... // eq , hashCode ,toString
      m3 = Class.forName("AAA").getMethod("sleep", new Class[0]);  
    ...  
  }  



最后 我们再看 aaa.sleep() 的调用



aaa.sleep()  实际就是  $Proxy0.sleep()  




return (Object)this.h.invoke(this, m3,null); 



从上面的截图中可以看出 h 就是 PersonProxyHandle


invoke(this.m3,null) 就是 下面的这段代码(上面已经贴过)


	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object res = null ;
		before();  //  之前 
		res = method.invoke(obj, args);
		after();  //  之后
		return res;
	}



中间在利用反射 调用具体的方法


下面的就不用说了吧 . . .


号外 : Proxy.newInstance( .... ), 这个方法就是返回指定的 代理类,里面的具体的单个参数就是给java虚拟机在程序运行时 生成代理类使用的;


优点 我就不多说了 ,扩展性 ,灵活性是不是提升了好几呗,


说下缺点 :


每一个都要定义一个接口 :这就是 java jdk 自带的动态代理模式的一个限制;


后面再研究下 cglib 。


听说 Spring AOP 就是用这个JDK Proxy实现的,不过我没有看过源码,只看过一些资料。。。


参考文章 :


  • http://www.daidingkang.cc/2017/07/18/java-reflection-annotations/
  • http://blog.csdn.net/u013476542/article/details/53228552?locationNum=6&fps=1
  • http://blog.csdn.net/jianghuxiaoxiami/article/details/3403924
  • http://www.jb51.net/article/125621.htm

?

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表