Java设计模式之代理模式

转载请注明出处:http://blog.csdn.net/li0978/article/details/53233924

说起Java代理模式,不明其理可能一头雾水,明白了也就很简单了。举一个生活中最典型的例子:律师代替原告打官司,来看一下过程:

  1. 原告准备做哪些事:原告可以提供很多材料给律师,目的是打官司赢地皮。
  2. 律师二次包装:律师利用自身丰厚的经验根据这些材料帮着打。
  3. 原告有信息不愿透漏:律师只能根据原告提供的信息进行辩论,原告也许有暗黑的一面,不愿意让律师说出来。

最终结果:律师能否打赢,注重看律师的水平,当然离不开原告所提供资料的充分和真实。

代理模式定义

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。

在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

移动端开发的时候最常见的一种代理形式就是远程加载图片,可以先加载一个小图,根据用户意愿如果用户愿意加载原图再开启线程进行加载大图,这里的小图就是代理者。

代理模式结构

按照代理模式定义只需要两个角色即可,一个是的源对象,另一个是代理者。但是通常代理者需要代替源对象做一些事情,所以我们需要把这部分共有的功能给抽象出来,这样还有一个好处就是可以很好地隐藏和保护被代理者。结构如图:
代理模式图示

代理模式的使用环境

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

静态代理和动态代理

按照代理类生成的时间我们可以将代理模式分为静态代理和动态代理。

静态代理,就是程序员事先已经将代理类实现并编译,然后在主程序运行时直接调用即可。

动态代理,代理类字节码在程序运行期间由jvm根据反射机制动态生成,代理者和被代理者的关系是在程序运行中确定的。

静态代理的两种实现:

1.静态代理的采用继承方式的实现过程:

package com.reflect;

/**
 * 被代理者和代理者所需要实现的接口
 */
interface ISay {
	void say(String name, int age);
}

/**
 * 被代理者
 */
class Person implements ISay {

	@Override
	public void say(String name, int age) {
		System.out.println("名字叫" + name + ",年龄" + age);
	}
}

/**
 * 代理者
 */
class ProxyPerson extends Person {

	// 重写父类的方法
	@Override
	public void say(String name, int age) {
		System.out.println("代理人说出了这个人的信息:");
		super.say(name, age);
	}
}

public class Test {
	public static void main(String[] args) {
		ISay proxyPerson = new ProxyPerson();
		proxyPerson.say("张三", 18);
	}
}

上边只是一个代理对象,假如有多个代理对象并且有一个先后运行的顺序,那么采用继承的方式将显的有点笨拙了。静态代理可以看做是一个嵌套运行对象方法的模式,知道了这一点,我们还可以采用聚合的方式去写,这样会显的相对灵活些。

2.静态代理的采用聚合方式的实现过程:

package com.reflect;

/**
 * 被代理者和代理者所需要实现的接口
 */
interface ISay {
	void say(String name, int age);
}

/**
 * 被代理者
 */
class Person implements ISay {

	@Override
	public void say(String name, int age) {
		System.out.println("名字叫" + name + ",年龄" + age);
	}
}

/**
 * 代理者
 */
class ProxyPerson implements ISay {
	private Person person;
	
	public ProxyPerson(Person person){
		this.person = person;
	}

	@Override
	public void say(String name, int age) {
		System.out.println("代理人说出了这个人的信息:");
		person.say(name, age);
	}
}

public class Test {
	public static void main(String[] args) {
		ISay proxyPerson = new ProxyPerson(new Person());
		proxyPerson.say("张三", 18);
	}
}

静态代理的优缺点:

优点:层次分明,如果需要增加更多的代理功能只需要关注代理类即可,另外在程序运行时要先进行编译,保证程序的准确性。

缺点:

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的对象很多,势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理实现过程:

动态代理似乎正是为了上边静态代理那两大缺点做准备的。动态代理的将代理类中的所有的代理方法全部封装到一个方法中,处理起来更加便捷。另外在某种情况下更是减少部分代理类的代码的编写。

动态代理实现过程主要分三步:

  1. 实现InvocationHandler接口创建处理器类,并在其内部重写invoke()方法,将来所有的要代理的方法都要在此方法中进行调用。
  2. 获取被代理者的实例。
  3. 拿到被代理者的方法对象调用代理方法。

动态代理的代码实现过程:

package com.reflect;

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

/**
 * 被代理者和代理者所需要实现的接口
 */
interface ISay {
    void say(String name,int age);
}

/**
 * 被代理者
 */
class Person implements ISay {

    @Override
    public void say(String name, int age) {
        System.out.println("名字叫" + name + ",年龄" + age);
    }
}

/**
 * 控制动态代理的处理器
 */
class MyInvocationHandler implements InvocationHandler {

    private Object object = null;

    /*
     * 获取代理者
     */
    public Object bind(Object object) {
        //得到被代理者实例
        this.object = object;
        //根据被代理者返回动态代理类实例,第一个参数是指定类加载器,第二个参数是指定代理类需要实现的一系列接口,第三个参数是控制动态代理的处理器本身实例
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
    }

    /*
     *代理者需要实现的代理方法 
     *参数一:代理者实例(上边bind方法返回的);
     *参数二:被代理者调用的方法对象
     *参数三:被代理者传入的相关参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("代理人说出了这个人的信息:");
        method.invoke(this.object, args);
        return null;
    }

}

public class Test {
    public static void main(String[] args) throws Exception {
        MyInvocationHandler invocationHandler = new MyInvocationHandler(); 
        ISay proxyPerson= (ISay) invocationHandler.bind(new Person());
        proxyPerson.say("张三", 18);
    }
}

动态代理代理者对象生成分析

看到上面代码有同学可能会问,那动态代理的代理者对象是如何生成的呢?我们不妨逆势而上而观之,先从传入参数下手,依次向上推。从底层得知其实获取代理者对象也就四步:

第一步:实现InvocationHandler接口创建自己的调用处理器,将来作为代理者有参构造器的一个参数。

InvocationHandler h = new InvocationHandler();

第二步:根据被代理者classload()和相关代理接口获取代理者Class类实例。

Class<?> cl = getProxyClass0(loader, interfaces);

第三步:根据第二步拿到的代理者实例对象获取代理者内部构造函数。

Constructor<?> cons = cl.getConstructor(constructorParams);

第四步:根据代理者内部构造函数最终拿到代理者对象。

cons.newInstance(new Object[] {h} );

最终得到代理者对象其实就是这一句:

Object object = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);

总结

以上就是我对代理模式的认识,代理模式的优点就是能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。当然缺点也是存在的,由于在客户端和真实主角之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式更多的是需要额外的工作,这种情况下有些代理模式的实现却显得非常复杂了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值