Back
Featured image of post 设计模式-代理模式

设计模式-代理模式

一、模式简介

说起代理模式相信大家并不陌生,我们都知道Spring的AOP中使用使用的就是动态代理技术不管是Jdk的动态代理、还是cglib的代理相信大家都有一定理解。举个生活中的例子,在朋友圈中我们可以看到很多的微商招收代理,通过代理在朋友圈卖货,买家在朋友圈买到的东西大多都是从代理处下单的。代理模式:为另外一个对象提供一个替身或占位符以控制这个对象的访问。

二、模式详解

​ 在《HeadFirst设计模式》中说到代理模式创建代表,让代表对象控制某对象的访问,被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。从这个介绍中我们可以看到代理模式可以生成许多种不同的代理对象,那么主要分为哪些代理对象呢?

  • 远程代理:就是将工作委托给远程对象(不同的服务器,或者不同的进程)来完成。常见的是用在web Service中。还有就是我们的RPC调用也可以理解为一种远程代理。
  • 保护代理:基于权限控制对资源的访问
  • 虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候再进行创建
  • 智能代理:提供对目标对象额外的服务(也是平时设计最常用的)

我们可以看到针对于不同种场景会生成不同的代理,而在平时较为常用的就是智能代理。下面我们来看下代理模式的类图:

  • Subject(共同接口):客户端使用的现有接口,通过实现统一接口Proxy在RealSubject出现的地方取代它。
  • RealSubject(真实对象):真实对象的类,它是被Proxy代理和控制访问的对象。
  • ProxySubject(代理对象):代理类,持有RealSubject的引用。在某些例子种,Proxy还会负责RealSubject对象的创建与销毁。

从类图中我们可以看到类图还是非常简单的Proxy持有RealSubject的引用,所以任何用到RealSubject的地方,都可以用Proxy取代。代理模式中基于实现的方式又主要分为:

  • 静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 动态代理:动态代理类是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

接下来我们分别演示一下静态代理和动态代理:

静态代理

  • subject接口
public interface Subject {
    //共同接口
    void doSomething();
}
  • 真实对象
public class RealSubject implements Subject{
    @Override
    public void doSomething() {
        System.out.println("我要去吃个饭");
    }
}
  • 代理对象
public class ProxySubject implements Subject{
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
    public ProxySubject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        this.realSubject = (RealSubject) this.getClass().getClassLoader().loadClass("com.example.test.demo.proxy.RealSubject").newInstance();
    }
    @Override
    public void doSomething() {
        realSubject.doSomething();
    }
    public static void main(String[] args) {

        try {
            // 第一种方式
            new ProxySubject().doSomething();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 第二种方式
        new ProxySubject(new RealSubject()).doSomething();
    }
}

通过静态代理的代码,可能我们会觉得它和装饰者是不是有些相似呢?下面我为大家来分析一下:

  • 从目的上:装饰模式是为装饰的对象增强功能,而代理模式对代理的对象施加控制。

  • 从功能上:装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案,代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用。

我们清楚静态代理是再编译器便确定了代理和委托的关系,而通常静态代理的使用场景却非常的少,那么静态代理有什么缺点呢?

我们可以发现代理对象的一个接口只服务于一种对象,并且如果在共同接口中加入新功能的话代理对象的代码还需要进行更改,耦合度比较高并且不易维护,因此静态代理使用也比较少

动态代理

我们了解了静态代理之后下面来看下动态代理的是如何实现的,这里我们主要讲述Jdk的动态代理,关于关于动态代理的详细分析后续文章中会进行更新,我们先来看下动态代理的类图:

通过类图我们可以发现Proxy并不是直接关联真实的对象,而是放在InvocationHandler中,其实InvocationHandler的工作就是响应代理的任何调用,也可以把它当作代理收到方法调用后,请求做实际工作的对象。下面以一个例子来解释来让大家对代理的行为控制有着更鲜明的感受:

  • Subject接口
public interface PersonBean {
    /**
     * 获取玩家名称
     *
     */
    String getName();
    /**
     * 获取等级
     *
     */
    Integer getGrade();

    /**
     * 获得武器
     *
     */
    String getWeapons();

    /**
     *  设置名称
     */
    void setName(String name);
    /**
     * 设置玩家等级
     *
     */
    void  setGrade(Integer grade);
    /**
     * 设置玩家武器
     *
     */
    void setWeapons(String weapons);
}
  • RealSubject
public class Person implements PersonBean{
    private String name;
    private int grade;
    private String weapons;
    @Override
    public String getName() {
        return name;
    }

    @Override
    public Integer getGrade() {
        return grade;
    }

    @Override
    public String getWeapons() {
        return weapons;
    }

    @Override
    public void setName(String name) {
        this.name=name;
    }

    @Override
    public void setGrade(Integer grade) {
    this.grade=grade;
    }

    @Override
    public void setWeapons(String weapons) {
        this.weapons=weapons;
    }


}
  • InvocationHandler
public class PersonInvocationHandler implements InvocationHandler {
    private PersonBean person;

    //当然这里也可以使用反射来创建Person
    public PersonInvocationHandler(PersonBean person){
        this.person=person;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //如果客户端访问设置等级方法,代理对象应当拒绝进行权限控制
            if (method.getName().equals("setGrade")){
                throw new IllegalAccessException();
            }else {
                return method.invoke(person,args);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
  • Proxy和测试
public class Test {
    public static void main(String[] args) {
        PersonBean proxy = getProxy(new Person());
        //调用没有权限的方法
        proxy.getName();
    }

    /**
     * 获取代理实例
     *
     * @param person 人
     */
    static PersonBean getProxy(PersonBean person){
        return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(),new PersonInvocationHandler(person));
   }
}

通过代码我们可以看到我们所做的就是使用代理对象控制真实对象,以便控制设置真实对象等级的方法,使暴漏给客户端的代理对象用户无法设置对象的等级。那么相较于静态代理使用动态代理的方式代理逻辑与业务逻辑是互相独立的,并且减少了静态代理产生的耦合。

三、适用场景

  • 访问控制( 保护代理)。如果你只希望特定客户端使用服务对象,这里的对象可以是操作系统中非常重要的部分,而客户端则是各种已启动的程序包括恶意程序),此时可使用代理模式

  • 记录日志请求(日志记录代理)。适用于当你需要保存对于服务对象的请求历史记录时

  • 延迟初始化(虚拟代理)。 如果你有一个偶尔使用的重量级服务对象,一直保持该对象运行会消耗系统资源时,可使用代理模式。

  • 缓存请求结果 (缓存代理)。适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。