理解Java设计模式—代理模式

通过学习知乎上的一些高赞回答和一些博客,自我总结的一篇关于AOP前置技能——代理模式的博文,内容部分引用自代理模式

代理模式

  • 定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介

  • 结构图:

  • 使用代理方式的优点:

    • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口
    • 开闭原则,增加功能 :代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

静态代理

有一个打印机的类

public class Printer {
    public void print(){
        System.out.println("打印!");
    }
}

如果想在打印之前先记录一下日志怎么做?

最简单的方法:在打印的功能前面直接加上记录日志的功能。

public class Printer {
    public void print(){
        System.out.println("记录日志!");
        System.out.println("打印!");
    }
  }

看上去好像没有问题,但是修改了打印机的源代码,破坏了面向对象的开闭原则,有可能影响到其它功能。怎么解决呢?很容易可以想到,既然不能修改原来的代码,那就新建一个类并继承原打印机

public class LogPrinter extends Printer {
    public void print(){
        System.out.println("记录日志!");
        System.out.println("打印!");
    }
}

这个类继承了打印机的类,重写了打印机的打印方法,并额外提供了记录日志的功能,以后需要打印机的时候使用这个类就好。

进一步优化,将“打印”功能抽象出来,即抽象出一个接口:

public interface IPrinter {
    void print();
}

打印机类拥有这个功能,那么需要实现这个接口:

public class Printer implements IPrinter {
       public void print(){
       System.out.println("打印!");
    }
}

同样打印机代理类也拥有该功能,所以也实现该接口,在构造函数中将打印机对象传进去,实现接口的打印方法时调用打印机对象的打印方法并在前面加上记录日志的功能:

public class PrinterProxy implements IPrinter {
    
    //代理类中需要创造一个打印机对象,即被代理对象
    private IPrinter printer;
    public PrinterProxy(){
        this.printer = new printer();
    }
    @Override
    public void print() {
        //添加打印功能
        System.out.println("打印之前-记录日志");
        printer.print();
        System.out.println("打印完毕-提醒");
    }
}

当外界,如职员使用打印机时可以不通过打印机类而是通过代理类来实现打印功能,

public class Test {
    public static void main(String[] args) {
        PrinterProxy proxy = new PrinterProxy();
        proxy.print();
    }
}

此时在职员眼中,与他直接交流的是这个代理类,打印机对他是透明不可见的;代理类内部通过调用打印机类的打印功能实现打印功能,在最终功能之前或之后能够附加中间功能

结果如下:

记录日志
打印
提醒

此时,打印机的代理类基本实现了我们的需求:①在不影响打印机源码的情况下添加了额外功能;②将真正的打印机与职员分隔

动态代理

背景

但是静态代理并不是完美无缺的,考虑以下场景,如果打印机还有复印功能,那么将如何实现呢?按照静态代理的逻辑,代码如下:

抽象通用接口:

public interface IPrinter{
    //打印功能
    void print();
    //复印功能
    void copy()
}

打印机类

public class Printer implement IPrinter(){
    public void print(){
        System.out.println("打印");
    }
    public void copy(){
        System.out.println("复印");
    }
}

代理类

public class PrinterProxy implement IPrinter(){
    private Printer printer;
    public  PrinterProxy(){
        this.printer= new Printer
    }
        
    public void print(){
        System.out.println("打印之前-记录日志");
        printer.print();
        System.out.println("打印完毕-提醒");
    }
    public void copy(){
        System.out.println("复印之前-记录日志");
        printer.copy();
        System.out.println("复印完毕-提醒");;
    }
}

此时,我们很明显的看到如果打印机有多个功能且每个功能都需要附加额外的功能,那么在代理类中就必须将打印机的功能复写多次并加上同样的额外功能,这是很繁琐的,所以如何少写或者不写代理类,却能完成代理功能? 在这种情况下,动态代理应运而生!

反射创建对象

在进行动态代理的讲解前需要明白一个概念:反射

通常的对象创建过程:

使用反射进行的对象创建过程

从上图中能看到,创建一个对象最关键的是要获得对应的Class对象

得到对应的的Class对象后就可以通过反射创建对应的实例了

动态代理

从上一节知道可以通过获得对应的Class对象,然后根据它创建实例,所以就解决了一大问题:在不写代理类的情况下创建代理实例,这就解决了静态代理的弊端

Class对象包含了一个类的所有信息,比如构造器、方法、字段中,如果不写对应的代理类,那么这些信息也无从获得,但是代理类和目标类实现了同一组接口,如打印机案例中的IPrinter接口,这些接口保证了代理类和目标类的内部在初始时是几乎相同的,所有对代理对象的操作最终可以转嫁到目标对象上,而代理对象只需专注于额外功能的攥写,如下:

通俗地讲:接口拥有代理类和目标类共同的类信息,所以可以从接口处获得本应由代理类提供的信息,按理来讲此时就可以通过接口处获得的接口Class对象创建实例,但是别忘了,接口是无法创建对象的,所以就需要用到下面的类

JDK提供了一个重要的类java.lang.reflect.Proxy,该类提供了一个方法getProxyClass(ClassLoader,interfaces)方法,这个方法可以通过传入的类加载器和一个接口直接返回一个代理Class对象

此时我们终于获得了代理Class对象,而后可以通过反射机制获得代理Class对象对应的代理类了,这样就直接跳过了代理类的创建,实际中也不使用getProxyClass(ClassLoader,interfaces)方法而是使用更便捷的Proxy.newProxyInstance()直接返回代理实例(将获得代理Class对象和通过反射获得代理类两步合二为一)

接下来还需要用到另一个接口java.lang.reflect.InvocationHandler ,这是在由代理Class对象创建代理实例时所需要实现的一个接口,代理Class的构造器创建实例时必须传入InvocationHandler ,作用是:每次调用代理对象的方法,最终都会调用InvocationHandlerinvoke()方法,invoke方法再调用目标对象的方法,即调用打印机类中的打印、复印方法;最重要的是:在invoke()方法前后就能够添加附加功能了,即解决了静态代理的一大弊端:如果目标对象的每个方法都需要添加额外方法,那么需要复习每一个方法并单独添加额外功能,此时通过invoke()方法就能对所有目标对象的方法统一添加额外方法了

接下来是代码实现:

抽象通用接口:

public interface IPrinter{
    //传真功能
    void fax();
    //打印功能
    void print();
    //复印功能
    void copy()
}

打印机类:

//多个方法
public class Printer implement IPrinter(){
    public void fax(){
        System.out.println("传真");
    }
    public void print(){
        System.out.println("打印");
    }
    public void copy(){
        System.out.println("复印");
    }
}

动态代理

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

//首先必须实现InvocationHandler接口
public class ProxyHandler implements InvocationHandler{
    
    //用来接收代理对象
    private Object proxy;
    
    //通过newProxyInstance获得目标对象的代理对象
    public Object getProInstance(Object proxy){
        this.proxy = proxy;
        //传入目标对象的类加载器、通用公共接口以及一个实现了InvocationHandler接口的类(this)
        return Proxy.getInstance(proxy.getClass().getClassLoader(),proxy.getClass,this)
    }
    
    //invoke方法,每次调用目标对象的方法(无论哪一个)时都会调用,method代表目标对象的方法
    public Object invoke(Object proxy , Method method , Object[] args)throws Throwable{
        Object result = null;
        
        System.out.println("记录日志");
        result = method.invoke(proxy,args)
        System.out.println("完毕");
        
        return result
    }
}

职员使用

public class TestProxy
{
    public static void main(String args[])
    {
        ProxyHandler proxy = new ProxyHandler();
        //动态获得代理对象
        IPrinter ipr = (IPrinter) proxy.getProInstance(new RealSubject());
        //通过代理对象调用方法
        ipr.copy();
        System.out.println("--------");
        ipr.print();
        System.out.println("--------");
        ipr.fax(); 
    }
}

结果:

记录日志
复印
完毕
--------
记录日志
打印
完毕
--------
记录日志
传真
完毕
posted @ 2020-07-23 12:34  言兴  阅读(138)  评论(0)    收藏  举报