设计模式——代理模式

一、 理解代理模式

之前由于用到AOP编程,便想把实现原理了解一下,没想到背后涉及到的东西很多,于是在这里总结一下,这也作为我学习设计模式的笔记,之前买了《Head First 涉及模式》这本书,还没有看完,借着这个系列,用到哪个模式就总结一下。
先说几个故事来感受一下代理模式到底是什么东西,大家都知道曹操挟天子以令诸侯,曹操不想大臣和诸侯直接向皇帝建言,于是所有的奏章和信息都先经过他,不管是大臣向皇帝陈事,还是皇帝向大臣下诏都要经过他,对他不利的东西都要销毁,他已经完全控制了皇帝,所以他就是皇帝在大臣或者全国的代理人。还有过去的买办,是替外国人在中国做生意,外国人和中国本地的商人做生意不管是语言、习俗还是政治环境都是不通的,所以把生意委托给既懂得如何和外国人打交道又是本地人的商人,如果没有它们,则外国人很难在当地发展,这样这些买办就控制了本地人和外商之间的交流,俗话说雁过拔毛,这些买办当然要做那些对自己利润大的生意,而对可能损害自己的利益的则毫无兴趣,所以这些人也可以看成外国人在中国的代理人,包括现在的代理人、经销商、加盟连锁都可以看成代理模式的实现,即它们都控制了对提供服务的对象的直接访问,而是以一种替身来代替以供访问。
代理模式:
  • 定义:为一个对象提供一个替身或者占位符以控制对这个对象的访问。被代理的对象可以是远程的对象,创建开销大的对象或者需要安全控制的对象。
  • 基本结构:基本的结构由一个公共接口Subject、一个实现类RealSubject,包含提供功能的真正的方法,一个是实现类Proxy,它是RealSubject的代理,持有RealSubject的引用,这两个类实现了相同的接口。根据不同的实现方式,还会有不同的结构。

二、 静态代理

所谓静态代理,最主要的特点就是代理类在程序运行前已经存在了,即*.class文件已经存在,由程序员或者自动工具生成源码,然后编译、运行。它忠诚地按照上面提出的基本结构来实现。看下面这个例子。
公共接口Subject:
package com.pattern.ProxyPattern;

public interface Subject {
    public String sell(int money);
}
实现类RealSubject:
package com.pattern.ProxyPattern;

public class RealSubject implements Subject{

    public String sell(int money) {
        return "OK";
    }
}
代理类Proxy:
package com.pattern.ProxyPattern;

public class StaticProxy implements Subject{
	private Subject subject;
	
	public StaticProxy(RealSubject realSubject) {
		 subject = realSubject;
	}
	
	public String sell(int money) {
		if(money > 100){
			return subject.sell(money);
		}
		else{
			throw new UnsupportedOperationException("the money is not enough");
		}
	}
}
测试代码:
 
 
RealSubject subject = new RealSubject();
StaticProxy proxy = new StaticProxy(subject);
		
System.out.println(proxy.sell(101));
System.out.println(proxy.sell(0));

静态代理的优缺点:
  • 优点:业务类只需要关注业务逻辑本身,并且该业务类可以重用,即该业务类可以有多个静态代理,差不多也是所有代理的优点;
  • 缺点:由于存在公共接口,每当接口中新增一个方法时,所有该接口的实现类都会实现该方法,无形中增加了代码的复杂度;另外如果在一个接口中方法很多,那么就要为每个方法编写一个相应的代理类,按照代理模式的定义,这本来没有什么问题,但是方法太多并且如果一些方法根本就用不到,或者为一些方法提供代理的逻辑是类似的,那就比较悲催了。

三、 动态代理

所谓动态代理,是相对于静态代理而言的,业务代理类是在程序运行中由JVM根据反射等机制动态生成的,所以在编译时并不存在相应的字节码文件,并且代理类和被代理的类之间的关系是在程序运行是确定的。
关于动态代理,有两种实现手段,一个是JDK提供的默认方式,另外一个是cglib。两者的区别如下:
  • JDK实现方式:这种方式仅仅支持接口代理,如果涉及到有些类并没有实现接口,则不能用这种方式来实现动态代理,而是用cglib;
  • cglib实现方式:可以支持接口代理,也可以对没有实现接口的类提供动态代理。Github项目网址
下面针对这两种方式提供两个例子:
使用JDK实现方式:注意使用这种方式,Proxy类会自动生成代理类,而不需要自己手动实现代理类。
关键的类和接口:
  • java.lang.reflect.InvocationHandler
  • java.lang.reflect.Proxy
例子如下:
public class JDKDynamicProxyHandler implements InvocationHandler{
	protected final Logger logger = LoggerFactory.getLogger(this.getClass());
	private RealSubject realSubject;
	
	public Object getJDKDynamicProxy(RealSubject realSubject){
		this.realSubject = realSubject;
		return Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), this);
	}

	// parameter proxy shouldn't be used because it can lead to recursion and throw StackOverflowError, endless loop
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		proxy.getClass().getName();
		
		if((method.getName().startsWith("sell"))){
			method.invoke(realSubject, args);
		}
		return null;
	}
}
所用的接口和被代理的对象和静态代理的相同。
这里面有一个问题要十分注意:在实现InvocationHandler时要实现的抽象方法invoke的参数,proxy是生成的代理类的对象实例,如果用这个对象去调用被代理对象的方法会导致递归,形成无限循环,直至出现StackOverflowError为止;但是我看了网上的好多文章,它们都几乎没有说明这一点,如果用proxy对象实例去调用自己的方法是不会有问题的,但是由于这个类是由JVM动态生成的,所以它内部生成了什么方法我们是不知道的,但是我们有反射呀(这简直是神器呀),具体内容自行脑补。
JDKDynamicProxyHandler jdkHandler = new JDKDynamicProxyHandler();
Subject subject = (Subject)jdkHandler.getJDKDynamicProxy(new RealSubject());
		
subject.sell(0);
subject.buy(0);
得到如下结果:

使用cglib实现方式:
关键的类和接口:
通过浏览网上的文章,看到大家基本上都是用MethodInterceptor,这个确实是cglib中的一个比较重要的接口,类似于JDK中的InvocationHandler的作用。但是它其中的其他类和接口也很重要,cglib甚至提供了几种不同的回调函数以供使用,下面给出了两种实例。
首先创建一个操作类DBOperation,没有继承和实现任何东西,实例如下:
public class DBOperation {
	protected Logger logger = LoggerFactory.getLogger(this.getClass());
	
	public String add(){
		logger.info("method add invoked");
		return "method add invoked";
	}
	
	public String delete(){
		logger.info("method delete invoked");
		return "method delete invoked";
	}
	
	public String update(String oldValue, String newValue){
		logger.info("method update invoked");
		return "method update invoked";
	}
	
	public String query(){
		logger.info("method update invoked");
		return "method update invoked";
	}
}
下面是使用cglib完成动态代理并进行测试的代码:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(DBOperation.class);
CallbackHelper callbackHelper = new CallbackHelper(DBOperation.class, new Class[0]) {
	@Override
	protected Object getCallback(Method method) {
		if(method.getName().startsWith("delete")){
			return new FixedValue() {
				public Object loadObject() throws Exception {
					return "intecept delete";
				}
			};
		}
		else {
			return new MethodInterceptor() {
				public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
					if(method.getName().startsWith("update")){
						logger.info("method parameters are {}", args);
						Object object = proxy.invokeSuper(obj, args);
						logger.info("return type is {}", method.getReturnType().getName());
						return object;
					}
					else{
						return proxy.invokeSuper(obj, args);
					}
				}
			};
		}
	}
};
enhancer.setCallbackFilter(callbackHelper);
enhancer.setCallbacks(callbackHelper.getCallbacks());
DBOperation operation = (DBOperation) enhancer.create();
		
operation.add();
operation.delete();
logger.info("invoke delete return value:  {}", operation.delete());
operation.update("123", "456");
结果如下:


上面只是cglib动态代理的一个简单的实例,但是其中的信息量还是有很多的。
  • 首先,就是我们之前提到的cglib可以对没有实现接口的类进行动态代理;
  • cglib提供了很多回调接口的实现,比如这里使用的FixedValue接口和MethodInterceptor接口,MethodInterceptor的使用方法类似于Invocationhandler,这里不再赘述,但是要注意的是,为了避免无限循环的问题,需要使用proxy.invokeSuper(obj, args)来实现这个功能,这是因为cglib的实现手段不同,它会创建一个要代理类的子类,而对于在JDK的实现方式中则不可以用method.invoke(proxy, args);
  • FixedValue接口会拦截所有的方法,并且这些方法的返回值都是loadObject()中的返回值,而且从上面的输出结果来看,被调用的方法由于被FixedValue的回调接口拦截,其内部的方法并没有执行。
总之cglib是一个非常强大的动态代理的库,并且据说其性能也要高于JDK的实现方式,不过由于没有试过,不敢确定,看那下面的链接会有说到。
另外cglib还提供了其他的功能,如Bean generator,想要了解具体的内容,请到Github自行学习, Github项目地址

四、 RMI

关于RMI之前一直不了解,这是个神马玩意,直到看了《Head First 设计模式》后,才发现其实这货也没有什么高深的东西,也是代理模式的应用,这里只是说一下代理模式在RMI中的应用,关于RMI的具体编程模型不展开介绍,留待以后专门写一篇文章进行介绍。RMI的行为类似于Web应用程序,客户端通过发送请求给服务端,服务端返回响应给客户端,但是要注意的是,它们两者还是有本质的区别的,RMI用于请求和响应的协议允许传递任意对象,它的客户端不一定是Web浏览器,而可以是任何程序,这个程序可以和人类进行交互,也可以不进行交互,而对于Web浏览器则是以人为服务对象的,并且只是用http协议。
下面用一张图来说明这个问题(图比较丑,但是道理该有的都有了),废话不说,上图:



五、 应用场景

  • 第一个我竟然想到了天朝的那个Great Wall,和我有相同想法的人一定不在少数;
  • 防火墙代理:控制网络资源的访问,以免使对象收到伤害;wiki
  • 缓存代理:为开销大的运算结果提供暂时存储,以减少运算或者网络延迟,如常见的Web缓存。

相关文章:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值