代理模式学习笔记

1.定义:代理模式是为一个对象提供一个代理或占位符,以控制此对象的访问的模式。

2.实际场景:在某些情况下,一个对象不合适或者不可以直接引用给另一个对象的时候,代理对象充当对客户端和目标对象之间的中介。客户端要访问一个对象,但不能直接调用这个对象,要通过此对象的代理来完成访问。

3.代理模式三要素:抽象接口、抽象接口实现类、实现类代理。实现类和代理都要实现抽象接口,代理内部包含实现类的引用,当客户端调用代理,代理在内部调用实现类,完成业务。关系如下(此图是网上找的,别找我麻烦):


参考链接1:http://www.blogjava.net/interface/archive/2008/01/04/172841.html?opt=admin(代理模式、动态代理)

参考链接2:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html(静态代理、动态代理、cglib)

参考链接3:http://www.cnblogs.com/cbf4life/archive/2010/01/27/1657438.html(强制代理、动态代理)

上面的链接已经足够理解代理模式了。但自己既然学习,也要照葫芦画瓢,自己试试。因此,我采用参考链接1中的应用场景自己试验一把。

静态代理

这里的场景买票。鄙人和盆友没空去买票,于是上网订个票,告诉一个XX订票公司要订张什么票,然后xx公司帮我们去买票。于是需求就来了:中华人民共和国14亿公民买票是一个抽象行为,而我们买票属于14亿分之3的几个实现类采取的具体行动,而XX公司,就是我们买票的代理。

代码如下:

买票抽象接口:

package proxy.business.buy;

public interface IBuyer {
	void buyTickets();
}
买票实现类(甲、乙、丙)

package proxy.business.buy;
public class BuyerJIA implements IBuyer {
	public void buyTickets() {
		System.out.println("我是甲,我要买张火车票!");
	}
}

package proxy.business.buy;
public class BuyerYI implements IBuyer {
	public void buyTickets() {
		System.out.println("我是乙,我要买张飞机票!");
	}
}

package proxy.business.buy;
public class BuyerBIN implements IBuyer {
	public void buyTickets() {
		System.out.println("我是丙,我要买张船票!");
	}
}
买票的公司,代理:

package proxy.stat;

import proxy.business.buy.IBuyer;
//静态代理实验类
public class StatProxy implements IBuyer {
	
	private IBuyer iBuyer;
	
	public StatProxy(IBuyer iBuyer){
		this.iBuyer = iBuyer;
	}

	public void buyTickets() {
		this.iBuyer.buyTickets();
		this.kuaiDi();
	}

	private void kuaiDi(){
		System.out.println("快递送票");
	}

}
于是,客户端访问给我们卖票时,卖给的是买票公司,但买票公司是帮我们买的,最后需求是属于自己的。客户端访问测试:

package proxy.test;

import proxy.business.buy.BuyerBIN;
import proxy.business.buy.BuyerJIA;
import proxy.business.buy.BuyerYI;
import proxy.business.buy.IBuyer;
import proxy.stat.StatProxy;

public class Client {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">		</span>IBuyer jia = new BuyerJIA();
<span style="white-space:pre">		</span>IBuyer yi = new BuyerYI();
<span style="white-space:pre">		</span>IBuyer bin = new BuyerBIN();
<span style="white-space:pre">		</span>StatProxy jiaProxy = new StatProxy(jia);
<span style="white-space:pre">		</span>jiaProxy.buyTickets();

<span style="white-space:pre">		</span>StatProxy yiProxy = new StatProxy(yi);
<span style="white-space:pre">		</span>yiProxy.buyTickets();

<span style="white-space:pre">		</span>StatProxy binProxy = new StatProxy(bin);
<span style="white-space:pre">		</span>binProxy.buyTickets();
<span style="white-space:pre">	</span>}
}
运行结果:

我是甲,我要买张火车票!
快递送票
我是乙,我要买张飞机票!
快递送票
我是丙,我要买张船票!
快递送票
这里实验的是静态代理,就是我的代理类需要采用实际代码编写一个class文件实现。

静态代理小结:

1)要说Client的main方法访问Buyer实例,与直接new一个新的Buyer实例,然后调用,执行结果有什么区别?
结果是没区别。但代理模式的定义是:代理对象控制对真实对象的访问。
如下面的代码,调用甲、乙、丙的“买票”方法,都是通过代理来实现的。
通过代理可以隐藏一些真实对象的功能(在代理类的实现方法中,不调用:this.realBuyer.xxMethod());或在代理类中添加一些扩展功能,如买完票后,快递寄票。
2)代理类说明:

在这里,一个静态代理类,内置的realBuy对象采用接口的形式,运行时,构造函数传入的参数是具体的子类,即代理中实际引用的对象是具体子类,但具体引用的是哪个子类是运行时决定的。这样,对同一接口的不同实现类,静态代理类的class文件只需要一个。

3)静态代理的缺陷
这里一个代理类statProxy,内置了一个IBuyer接口的子类对象。也就是说静态代理与接口是强关联的。现在,statProxy代理只有一个功能买票。如果新业务产生,比如说替人回信,那又需要新写一个关于代人回信的代理类。如果需要代理的功能很多,且代理的功能分布在多个接口中,那么就要写非常多的代理类。具体往下看。于是引入动态代理(动态代理尤其适用于框架建设,如spring aop,因为业务类是根据需求变化的,我们不可能要求框架为每个新增业务类再写一个代理类)。

动态代理

好了。上面测试了静态代理。现在来测试动态代理。动态代理涉及到两个JDK自带的类:java.lang.reflect.InvocationHandler接口、java.lang.reflect.Proxy类

关于这两个类的说明,可参见参考链接1参考链接2。此处直接进入测试。

现在,还是针对买票的功能实现动态代理。此时我们保留的类有:买票抽象接口、买票实现类。买票代理是动态生成(即所谓的动态代理),不需用代码来创建。但需要增加一个用来生成动态代理的类DynmProxyFactory。DynmProxyFactory代码如下:

package proxy.dynm;

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

public class DynmProxyFactory implements InvocationHandler {

	private Object target;
	
	/**
	 * 返回一个动态代理
	 * @param target
	 * @return
	 */
	public Object getProxy(Object target){
		this.target = target;
		//动态代理的实现要绑定接口。真实类如果没有实现接口,并不能实现动态接口。
		//如果要实现真实类的动态代理,采用cglib
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), this);
	}
	
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		this.doBefore();
		Object result = method.invoke(target, args);
		this.doAfter();
		return result;
	}
	
	private void doBefore(){
		System.out.println("真实类方法调用前..");
	}
	private void doAfter(){
		System.out.println("真实类方法调用结束。");
	}
}
然后,客户端访问动态代理的代码如下:

package proxy.test;

import proxy.business.answer.AnswerJIA;
import proxy.business.answer.AnswerYI;
import proxy.business.answer.IAnswer;
import proxy.business.buy.BuyerBIN;
import proxy.business.buy.BuyerJIA;
import proxy.business.buy.BuyerYI;
import proxy.business.buy.IBuyer;
import proxy.dynm.DynmProxyFactory;

public class DynmClient {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		DynmProxyFactory proxyFactory = new DynmProxyFactory();
		IBuyer bJia = (IBuyer) proxyFactory.getProxy(new BuyerJIA());
		bJia.buyTickets();

		IBuyer bYi = (IBuyer) proxyFactory.getProxy(new BuyerYI());
		bYi.buyTickets();

		IBuyer bBin = (IBuyer) proxyFactory.getProxy(new BuyerBIN());
		bBin.buyTickets();		
	}
}
测试结果:
真实类方法调用前..
我是甲,我要买张火车票!
真实类方法调用结束。
真实类方法调用前..
我是乙,我要买张飞机票!
真实类方法调用结束。
真实类方法调用前..
我是丙,我要买张船票!
真实类方法调用结束。
到这里,我们可以比较一下静态代理和动态代理的Client和DynmClient调用方式。看起来没什么区别。那如果需求有变,说对于买票者(委托人)之间,可以互发消息(与买票完全无关),委托人可以指定代理,代为回复收到的消息。

现在想想,要实现此功能,代人回复的抽象接口、和委托人实现类是必须要新建的。那如何使用代理呢?如果用静态代理,那原来的statProxy是没用了,只能在创建一个静态代理,然后在代理中包含代人回复抽象接口的实现类,通过调用实现类,完成消息回复。静态代理的代理类必须新增(因为静态代理与其代理的接口,是强关联的);现在考虑动态代理,我们不需要增加一个代理类,就可以完成回复功能的代理访问。

代人回复抽象接口代码:

package proxy.business.answer;

public interface IAnswer {
	void replyMessage(String name);
}
委托回复人实现类(甲、乙):

package proxy.business.answer;

public class AnswerJIA implements IAnswer {

	public void replyMessage(String name) {
		System.out.println("你好:"+name+"。我是甲,谢谢你的来信!");
	}
}
package proxy.business.answer;

public class AnswerYI implements IAnswer {

	public void replyMessage(String name) {
		System.out.println("你好:"+name+"。我是乙,对你的来信感激不尽!");
	}
}
采用动态代理的方式,实现代人回复的功能,客户端代码:

package proxy.test;


import proxy.business.answer.AnswerJIA;
import proxy.business.answer.AnswerYI;
import proxy.business.answer.IAnswer;
import proxy.business.buy.BuyerBIN;
import proxy.business.buy.BuyerJIA;
import proxy.business.buy.BuyerYI;
import proxy.business.buy.IBuyer;
import proxy.dynm.DynmProxyFactory;


public class DynmClient {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">		</span>DynmProxyFactory proxyFactory = new DynmProxyFactory();
<span style="white-space:pre">		</span>//创建动态代理,代人买票
<span style="white-space:pre">		</span>IBuyer bJia = (IBuyer) proxyFactory.getProxy(new BuyerJIA());
<span style="white-space:pre">		</span>bJia.buyTickets();

<span style="white-space:pre">		</span>IBuyer bYi = (IBuyer) proxyFactory.getProxy(new BuyerYI());
<span style="white-space:pre">		</span>bYi.buyTickets();

<span style="white-space:pre">		</span>IBuyer bBin = (IBuyer) proxyFactory.getProxy(new BuyerBIN());
<span style="white-space:pre">		</span>bBin.buyTickets();
<span style="white-space:pre">		</span>//创建动态代理,代人回复
<span style="white-space:pre">		</span>IAnswer aJia = (IAnswer) proxyFactory.getProxy(new AnswerJIA());
<span style="white-space:pre">		</span>aJia.replyMessage("小丽");
<span style="white-space:pre">		</span>
<span style="white-space:pre">		</span>IAnswer aYi = (IAnswer) proxyFactory.getProxy(new AnswerYI());
<span style="white-space:pre">		</span>aYi.replyMessage("花花");
<span style="white-space:pre">	</span>}
}
运行结果:

真实类方法调用前..
我是甲,我要买张火车票!
真实类方法调用结束。
真实类方法调用前..
我是乙,我要买张飞机票!
真实类方法调用结束。
真实类方法调用前..
我是丙,我要买张船票!
真实类方法调用结束。
真实类方法调用前..
你好:小丽。我是甲,谢谢你的来信!
真实类方法调用结束。
真实类方法调用前..
你好:花花。我是乙,对你的来信感激不尽!
真实类方法调用结束。
如此可见,动态代理是一个通用代理工厂,可为一个或多个接口生成指定的代理类,而不需要手动编写一个代理class。
关于静态代理和动态代理的思考:

1)动态代理相较静态代理,优势是只需要一个class类就能为所有接口动态创建代理。那还需要静态代理吗?什么业务情况下,不使用动态代理,而只使用静态代理?

静态代理是个性化的代理,若代理中需要完成特定于业务的逻辑功能,那只能使用静态代理。如上面的例子,买了票以后,我需要公司给我寄过来。“邮寄”这一个特定功能就属于一个个性化功能。动态代理如果写入类似的个性化功能,那动态代理就不在通用。

2)那何时使用动态代理?

动态代理的实现可参考spring aop。对于一些通用的,零散的方法,建立一个切面,定义切入点,对此切入点织入切面通用功能。那何时使用动态代理,就与何时使用aop的考虑一样,对于特殊日志记录、登陆后邮件确认等通用的,一般化的功能,则可用动态代理。

3)InvocationHandler接口实现类中的invoke方法,在客户端中并未调用,是JDK自动执行的。为什么不把此方法设置为私有方法?

个人理解是,java如此设计,就可以将被代理对象的方法暴露出来,这样在方法前后就可以做一些额外的处理。也是aop能实现的重要原因。

4)动态代理灵活的原因?

动态代理生成采用java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法生成代理,方法有三个角色:真实角色(loader)、抽象接口(interfaces)、被调用的方法(h)。

一个动态代理类,可以为不同的抽象接口动态生成代理,角色二可变(买票、替人回答);对于某一个实现了抽象接口的真实角色,此真实角色可变(甲、乙、丙),即角色一可变;代理调用真实角色的方法,可以在方法前后完成一些其他业务逻辑,增强功能,即角色三也可变。这样,动态代理中的三个角色皆可变,所以很灵活,易扩展。

5)动态代理的缺陷

动态代理生成参数中,绑定了被代理类的接口。如果没有接口,则动态代理不能实现。

为了实现针对一个实现类的代理,可采用cglib。

cglib

具体参看 参考链接2








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值