代理模式

来源:互联网 发布:winamp for mac 编辑:程序博客网 时间:2024/06/15 08:34

定义

        为其他对象提供一个代理以控制对这个对象的访问。

        在使用者与被使用者之间添加一层,以控制对被使用者的访问,添加一些额外的逻辑等。一般来说,两者都需要实现相同的接口。

分类

        远程代理:为一个对象在不同的地址空间提供局部代理,如Android中的IPC通信,本进程拿到的对象就是别的进程的一个本地代理。此时代理对象负责对请求及参数进行编码,然后发送到远程的对象中完成请求。

        虚代理:根据需要创建开销很大的对象。即某些对象的创建并不在一开始的时候完成,而是在第一次使用的时候进行。代理对象可缓存实体的一些信息,以便在实体未创建的时候也不会影响外界对某些功能的使用。

        保护代理:控制对原始对象的访问。检查调用者是否具备访问实体的权限。

关键词

        远程——IPC时,不同进程之间的对象都是远程对象在本进程的代理。

        延迟——虚代理。

        保护——检查访问者是否具备相应的权限。

比较

与装饰模式

        0,两者都描述了怎样为对象提供一定程度的间接引用,两者的实现部分都保留了指向另一个对象的指针,并且可以向这个对象发送请求。

        1,装饰为对象添加一个或多个功能。而代理则控制对对象的访问,并且有可能拒绝让实体执行某些方法——在没有权限的时候。

        2,装饰模式支持递归组合——递归组合是装饰模式中不可缺少的一部分,通过递归可以为一个对象动态地添加多个功能;但代理不支持递归组合,它只强调一种关系。

        3,装饰模式中,实体类定义了一部分功能,而由装饰类完全其余的功能;代理模式中,实体类提供了关键功能,而代理类提供(或拒绝)对它的访问。

与适配器模式

        代理模式与适配器模式中的对象适配类似,但适配器将一个对象转换为客户端需要的另一个接口,适配器与被适配类具有不同的接口,而代码中代理类与被代理类往往具有相同的接口。

普通代理

基础版

        比如想统计某个方法的执行时间,简单点可以直接修改原方法,在方法开始前输出时间,结束前输出时间。但这种思路直接修改了原方法,而且在原方法中添加了额外的逻辑处理。此时就可以使用代理模式:

        代理类持有一个被代理类的引用,并且两者实现同样的接口,只不过代理类的所有方法都交于被代理类来执行。示例如下:

//被代理类与代理类要实现的接口——也可定义成抽象类public interface ISubject {void doSth();}//被代理类public class RealSubject implements ISubject {@Overridepublic void doSth() {System.out.println("被代理类执行了具体的逻辑");}}//代理类public class SubjectProxy implements ISubject {private ISubject subject;public SubjectProxy(ISubject subject) {//传递一个被代理类的对象this.subject = subject;}@Overridepublic void doSth() {subject.doSth();}}//Client端使用代码public static void main(String[] args) {ISubject subject = new RealSubject();SubjectProxy proxy = new SubjectProxy(subject);proxy.doSth();}
        上面就是一个普通代理模式的实现。

升级版

        在上面的代码中有一个问题:客户端可以不使用代理对象而直接使用被代码对象——也就是说客户端可以完全绕开代理。为解决该问题,可以在被代理类的构造方法中进行一定的判断或者使用团队的编码规范进行约束。示例如下:

//被代理类public class RealSubject implements ISubject {public RealSubject(ISubject proxy){if(proxy == null)throw new RuntimeException("木有定义代理");}@Overridepublic void doSth() {System.out.println("被代理类执行了具体的逻辑");}}//代理类public class SubjectProxy implements ISubject {private ISubject subject;public SubjectProxy() {//传递一个被代理类的对象this.subject = new RealSubject(this);}@Overridepublic void doSth() {subject.doSth();}}//Client端使用代码public static void main(String[] args) {SubjectProxy proxy = new SubjectProxy();proxy.doSth();}

        从上面可以看出,只是在被代理类的构造方法中传入了一个ISubject对象,如果没有指定代理类时就会抛异常从而结束运行。而相应的,代理类中只能自己new一个对象了。当然,这只是一个简单的约束条件,可以根据需要自行添加。

        普通代理的缺陷在于一个代理类一般只能实现一个接口对象的代理。

扩展

        从Client端的代码可以发现,客户端没有直接使用具体的被代理者中相应的方法,它也不知道具体代理的是哪个对象,而且调用了代理类中的方法。因此,可以直接修改代理类的实现逻辑,达到对被代理类的扩展。比如要实现上面的统计代码执行时间的功能,可修改代理类即可,如下:

public class SubjectProxy implements ISubject {private ISubject subject;public SubjectProxy(ISubject subject) {this.subject = subject;}@Overridepublic void doSth() {//只修改该方法,完全不用修改被代理类中的逻辑System.out.println("被代理类执行前执行:"+System.currentTimeMillis());subject.doSth();System.out.println("被代理类执行后执行:"+System.currentTimeMillis());}}

        从上面可以看出,代理类并不简简单单的就是一个代理类,它可以实现自己的逻辑、职责,从而达到对被代理类方法的增强。上面的在调用doSth()之前和之后进行的操作就属性代理类自己的逻辑、职责,也是对被代理类的doSth()方法的增强。

强制代理

        上述的示例中,调用者只知道代理者而不知道被代理者。而强制代理却与之正好相反:调用者必须通过被代理者找到代理者,才能进行访问。因此,必须对ISubject进行修改,添加一个getProxy()方法,这样才能保证每一个被代理者都能获取到它的代理者。如下:

//被代理类与代理类要实现的接口——也可定义成抽象类public interface ISubject {void doSth();ISubject getProxy();}//被代理类public class RealSubject implements ISubject {private ISubject proxy;@Overridepublic void doSth() {if (proxy == null) {System.out.println("你没有使用代理者");} else {System.out.println("被代理类执行了具体的逻辑");}}@Overridepublic ISubject getProxy() {//获取自己的代理者的方法this.proxy = new SubjectProxy(this);return this.proxy;}}//代理类public class SubjectProxy implements ISubject {private ISubject subject;public SubjectProxy(ISubject subject) {this.subject = subject;}@Overridepublic void doSth() {subject.doSth();}@Overridepublic ISubject getProxy() {return this;}}//Client端使用代码public static void main(String[] args) {ISubject subject = new RealSubject();ISubject proxy = subject.getProxy();proxy.doSth();}

动态代理

        无论是普通代理还是强制代理都有一个缺陷:一个代理类能处理的接口是固定的,在编码阶段就固定了。而动态代理是在实现阶段不用关心代理谁,而在运行阶段才确定代理哪一个类。

        由普通代理和强制代理可以知道,代理类必须实现被代理接口中的所有方法,所以动态代理类必须实现任意指定接口中的方法。如何实现?默认情况下,所以方法都空实现即可——因为代理类不可能知道具体的处理逻辑,所以只能空实现。这一功能就需要通过Proxy类来完成。

       代理类空实现任意一个接口中的所有方法,这是毫无意义的,因此必须在代理类实现的方法中与客户端自己的操作关联起来,这个功能是通过InvocationHandler接口完成的。

        综上,动态代理分为两个过程:一、实现任意指定的接口,通过Proxy完成;二、客户端实现自己的逻辑,并与代理类相关联,通过InvocationHandler接口完成。示例如下:

//被代理类要实现的接口——也可定义成抽象类public interface ISubject {void doSth();}//被代理类public class RealSubject implements ISubject {@Overridepublic void doSth() {System.out.println("被代理类执行了具体的逻辑");}}//处理逻辑,以实现对被代理者方法的增强public class ProxyHandler implements InvocationHandler {private Object obj;public ProxyHandler(Object obj){//指定一个被代理者this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("这是具体处理代理逻辑的地方:"+method.getName());return method.invoke(obj, args);}}//Client端使用代码public static void main(String[] args) {ISubject subject = new RealSubject();//被代理对象ProxyHandler handler = new ProxyHandler(subject);//定义一个handler        //动态生成代理类ISubject proxy = (ISubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), new Class[]{ISubject.class},handler);proxy.doSth();//调用代理的方法}

        使用动态代理一想可以对被代理对象的方法进行增强,具体处理的地方就是handler中的invoke()方法。如上面示例在处理前添加了一条输出语句。

        另外,可以定义一个跟被代理对象相关的Proxy。可以新添加一个SubjectProxy类,如下:

public class SubjectProxy {public static ISubject newProxyInstance(ISubject sub){ProxyHandler h = new ProxyHandler(sub);return (ISubject) Proxy.newProxyInstance(sub.getClass().getClassLoader(), new Class[]{ISubject.class}, h);}}
客户端使用时,就非常简单:
ISubject subject = new RealSubject();ISubject proxy = SubjectProxy.newProxyInstance(subject);proxy.doSth();

动态代理的使用

        通过反射可以调用某个类中私有的方法,但有时候方法中的参数也是一个私有的接口对象。此时,不可以直接new一个接口对象然后传到invoke()中,但可以通过代理的方式创建一个该接口的代理对象,然后传到invoke()中。如下:

public class ProxyHandler {private ITest test;private void setTest(ITest test) {this.test = test;}public void ex() {test.test();}private interface ITest {public void test();}}

        对该类来说,可以通过反射调用setTest()方法,只不过在调用invoke()的时候没有办法传递一个ITest类型的对象——因为ITest是私有的,外部无法访问。为此,可以为ITest创建一个代理对象,并将该对象传递到invoke()中。如下:

try {ProxyHandler handler = new ProxyHandler();Class<?> name = Class.forName("com.baigle.test.itf.ProxyHandler$ITest");//获取setTest()的参数对应的Class类Method method = ProxyHandler.class.getDeclaredMethod("setTest",name);method.setAccessible(true);//为ITest创建代理对象,并在handler中对各种方法进行分别处理Object object = Proxy.newProxyInstance(name.getClassLoader(), new Class[]{name}, new InvocationHandler() {@Overridepublic Object invoke(Object arg0, Method arg1, Object[] arg2)throws Throwable {if("test".equalsIgnoreCase(arg1.getName())){//调用了test()方法System.out.println("调用了代理的方法了");return null;}return null;}});//调用setTest()方法,并将代理对象当作setTest()方法的参数method.invoke(handler, object);handler.ex();//调用public方法,此时会执行代理对象中的handler,进而执行自己的处理逻辑} catch (Exception e) {e.printStackTrace();}

        上面调用ex()方法时,会调用ITest对象中的test()方法,而ITest对象就是object这个代理对象,因此最终会执行到handler中的invoke()方法,并将执行的方法转变成Method传递到invoke()中。

        但该方法有一个弊端:不能在handler#invoke()中再执行arg1.invoke(arg0,arg2)。这是因为arg0本身就是一个代理对象,执行代理对象中的arg1方法,依旧会执行到handler#invoke()中。这就造成了一个死循环。




0 0
原创粉丝点击