代理模式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代理模式相关的知识,希望对你有一定的参考价值。

概述:

   代理模式,提供了对目标对象另外的访问方式。简单讲在不改变目标对象的提前下,为其添加额外功能以供其他对象使用。而对于开发人员来讲,其实就是不改变原有的代码,对相应功能进行扩展,比如限制对原有代码的访问权限,记录原有代码的执行时间,对运行过的代码写日志.....

  代理模式有静态代理和动态代理。其关键点是,代理对象与目标对象、代理对象是对目标对象的扩展,并调用目标对象。

代理模式的角色:

抽象对象:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。

目标对象:定义了代理对象所代表的目标对象。

代理对象:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象。

 

一、静态代理

静态代理有聚合和继承两种实现方式。下面我们以“一只程序猿在写代码为例子”,分别为其记录日志和时间。

1、继承实现方式:

//抽象对象:
public interface People {
    public void code();
}
//目标对象
public class Programmer implements People {
    @Override
    public void code() {
        System.out.println("我在写代码");
    }
}
//继承代理  
public class TimeProxy extends Programmer {
    public void proxy(){
        System.out.println("time Strat");
        super.code();
        System.out.println("time end");
    }
}

此时我们对程序猿的代理进行执行:

public class Client {
    public static void main(String[] args) {
        TimeProxy timpProxty = new TimeProxy();    
        timpProxty.proxy();    
    }
}

输出结果:

技术分享

这里代理模式(继承)就实现了,但这里有一个问题如果我要先记录对这只程序猿的行为的时间,然后为他记录日志。此时我们的代码可能是

public class LogAndTimeProxy extends  Programmer {
    public void logAndTimeProxy(){
        System.out.println("time start");
        System.out.println("log start");
        super.code();
    }    
}

但是如果需要先记录日志再记录时间,那此时就要修改原有的代理或者新增代理类,这种做法代码的实现不灵活。我们应该要做到分别定义一个时间代理,一个日志代理,不管时间、日志谁先谁后,都不需要去修改原有的代理或许添加新代理类

    

2、聚合实现方式:    

//抽象对象:
public interface People {
    public void code();
}
//目标对象
public class Programmer implements People {
    @Override
    public void code() {
        System.out.println("我在写代码");
    }
}
//时间代理类(聚合)
public class TimeProxy implements People {
    private People people;
    
    public TimeProxy(People people){
        this.people=people;
    }

    @Override
    public void code() {
        System.out.println("time start");
        people.code();
        System.out.println("time end");
    }
}
//日志代理类(聚合)
public class LogProxy  implements People {
    private People people;
    
    public LogProxy(People people){
        this.people=people;
    }
    
    @Override
    public void code() {
        System.out.println("logging start");
        people.code();
        System.out.println("logging end");
    }
}
//如果我们要先记录时间,后记录日志实现方式
public class Client {
    public static void main(String[] args) {
        People p = new Programmer();
        TimeProxy timpProxty = new TimeProxy(p);
        LogProxy logProxty = new LogProxy(timpProxty);
        logProxty.code();    
    }
}

执行结果:

技术分享

 如果要实现新记录日志再记录时间 

public class Client {
    public static void main(String[] args) {
        People p = new Programmer();
        LogProxy logProxty = new LogProxy(p);    
        TimeProxy timpProxty = new TimeProxy(logProxty);
        timpProxty.code();    
    }
}

结果:

技术分享

这样就可以不用修改代理类的情况下,更改代理类了。

但这里有一个问题就是每个代理类要继承一个接口或者实现一个类。如果一个项目比较大的话,那就需要添加很多的类,一旦接口增加方法,目标对象与代理对象都要维护。工作量会很大。

 

二、动态代理

动态代理有两种实现方法,分别是jdk代理和Cglib代理。

     Jdk代理主要实现Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)。其中ClassLoader 表示当前目标对象使用的类加载器,Class<?>表示目标对象实现的接口的类型,InvocationHandler 表示事件处理,当执行目标对象是会触发该方法。

    这里同样使用“一只程序猿在写代码,休息中...”为例子

//抽象对象:
public interface DynamicPeople {
    public void codeing();
    public void doOtherThing(String things);
}
//目标对象
public class DynamicProgrammer implements DynamicPeople {
    @Override
    public void codeing() {
        System.out.println("我在写代码...");
    }
    @Override
    public void doOtherThing(String things) {
        System.out.println(things+"...");
    }
}
//动态代理类
public class JdkDynamicProxy {
    public Object object;
    
    public JdkDynamicProxy(Object object){
        this.object=object;
    }

    //获取一个代理
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                object.getClass().getClassLoader(), 
                object.getClass().getInterfaces(), 
                new InvocationHandler(){
                    @Override
                 //这里proxy表示代理目标,methos表示方法,args表示参数
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        System.out.println("写日志");
                        //执行对应的方法
                        Object result =method.invoke(object, args);
                        return result;
                    }                
                });
    }
}

调用程序猿的方法,看看代理效果:

public class DynamicClient {
    public static void main(String[] args) {
        DynamicPeople programmer = new DynamicProgrammer();
        DynamicPeople proxy = (DynamicPeople) new JdkDynamicProxy(programmer).getProxyInstance();
        
        proxy.codeing();
        System.out.println("*************");
        proxy.doOtherThing("休息中");
    }
}

执行结果

 技术分享

    可见动态代理已经成功了。但这时我们需要实现上面所说的“分别定义一个时间代理,一个日志代理,不管时间、日志谁先谁后,都不需要去修改原有的代理或许添加新代理类”。这些我们需要改下newProxyInstance的InvocationHandler。我们分别定义时间处理类DynamicTimeHandler和日志处理类DynamicLogHandler,两者都继承InvocationHandler,代码如下:

public class DynamicTimeHandler implements InvocationHandler {
    //目标对象
    private Object object;
    public DynamicTimeHandler(Object object){
        this.object=object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {    
        System.out.println("开始时间");
        Object result = method.invoke(object, args);
        return result;
    }
}
public class DynamicLogHandler implements InvocationHandler {
    private Object object;
    public DynamicLogHandler(Object object){
        this.object=object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("开始日志");
        Object result = method.invoke(object, args);
        return result;
    }
}

修改代理类:

public class JdkDynamicProxy {
    public Object object;
    public InvocationHandler h;
    
    public JdkDynamicProxy(Object object,InvocationHandler h){
        this.object=object;
        this.h = h ;
    }

    //获取一个代理
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                object.getClass().getClassLoader(), 
                object.getClass().getInterfaces(), 
                h);
    }
}

调用程序猿的方法,看看代理效果     

public class DynamicClient {
    public static void main(String[] args) {
        DynamicPeople programmer = new DynamicProgrammer();
        InvocationHandler time = new DynamicTimeHandler(programmer);
        DynamicPeople timeProxy = (DynamicPeople) new JdkDynamicProxy(programmer,time).getProxyInstance();
        
        InvocationHandler log  = new DynamicLogHandler(timeProxy);
        DynamicPeople logProxy = (DynamicPeople) new JdkDynamicProxy(programmer,log).getProxyInstance();
        
        logProxy.codeing();
        System.out.println("*************");
        logProxy.doOtherThing("休息中");    
    }
}

执行结果:

 技术分享

   

    这样就实现了上面所说的效果了。但jdk的实现限制,就是目标对象必须实现一个接口。这个缺陷,我们用Cglib代理是可以解决的(Cglib在Spring的核心包中)。

这里我们以“开车去看美女”为例子

//定义一个目标对象汽车类
public class Car {
    public void running(){
        System.out.println("开着汽车...");
    }
    public void doSomething(String things){
        System.out.println("开着汽车"+things);
    }
}
//写代理方法
public class CglibDynamicProxy implements MethodInterceptor{
    public Object b;
    
    public CglibDynamicProxy(Object b){
        this.b=b;
    }
    
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(b.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();

    }

    @Override
    public Object intercept(Object arg0, Method method, Object[] arg2,
            MethodProxy arg3) throws Throwable {
            System.out.println("开始代理...");
            //执行目标对象的方法
            Object returnValue = method.invoke(b, arg2);
            return returnValue;
    }
}

调用方法,看一下执行效果

public class DynamicClient {
    public static void main(String[] args) {
        Car car = new Car();
        Car newCar= (Car) new CglibDynamicProxy(car).getProxyInstance();
        newCar.doSomething("看美女");
        newCar.running();
    }    
}

执行结果

 技术分享

对于如何“分别定义一个时间代理,一个日志代理,不管时间、日志谁先谁后,都不需要去修改原有的代理或许添加新代理类”还没研究,等以后补上,O(∩_∩)O

 

以上是关于代理模式的主要内容,如果未能解决你的问题,请参考以下文章

scrapy按顺序启动多个爬虫代码片段(python3)

用于从 cloudkit 检索单列的代码模式/片段

java代码实现设计模式之代理模式

代理模式(静态代理动态代理)代码实战(详细)

Java设计模式-代理模式之动态代理(附源代码分析)

代理模式(静态代理)