crazyandcoder

设计模式教程(7-代理模式)

2021.07.07

代理模式.png

1 静态代理

1.1 定义

在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到中介的作用。静态代理模式 (Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英 文叫做 Proxy 或 Surrogate,它是一种对象结构型模式。


代理 (Proxy) 是一种设计模式,定义:为其他对象提供一个代理以控制对某个对象的访问,即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.


静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。关键:在编译期确定代理对象,在程序运行前代理类的 .class 文件就已经存在了。比如:在代理对象中实例化被代理对象或者将被代理对象传入代理对象的构造方法

1.2 UML结构

静态代理.gif

  • Subject: 抽象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 真实主题角色

1.3 代码实例

1.3.1 Subject: 抽象主题角色

public abstract class Subject {
    abstract void request();
}

1.3.2 RealSubject: 真实主题角色

public class RealSubject extends Subject {

    @Override
    void request() {
        System.out.println("RealSubject request!!!");
    }
}

1.3.3 Proxy: 代理主题角色

public class ProxySubject extends Subject {

    private RealSubject realSubject = new RealSubject();

    @Override
    void request() {
        System.out.println("before invoke realSubject request !!!");
        realSubject.request();
        System.out.println("after invoke realSubject request !!!");
    }
}

1.3.4 Client 测试

public class Client {
    public void request() {
        Subject subject = new ProxySubject();
        subject.request();
    }
}


Client client = new Client();
client.request();

结果打印

before invoke realSubject request !!!
RealSubject request!!!
after invoke realSubject request !!!

1.4 优缺点

1.4.1 优点

  1. 可以做到在不修改目标对象的功能前提下,对目标功能扩展.

1.4.2 缺点

  1. 代理类和委托类实现相同的接口,同时要实现相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

2 动态代理

静态代理类无非是在调用委托类方法的前后增加了一些操作。委托类的不同,也就导致代理类的不同。那么为了做一个通用性的代理类出来,我们把调用委托类方法的这个动作抽取出来,把它封装成一个通用性的处理类,于是就有了动态代理中的 InvocationHandler 角色(处理类)。于是,在代理类和委托类之间就多了一个处理类的角色,这个角色主要是对代理类调用委托类方法的这个动作进行统一的调用,也就是由 InvocationHandler 来统一处理代理类调用委托类方法这个操作。从 JVM 角度来说,动态代理是在运行时动态生成 .class 字节码文件 ,并加载到 JVM 中的。这个我们在 Java 字节码生成框架中已经提到过。虽然动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助,Spring AOP、RPC 等框架的实现都依赖了动态代理。就 Java 来说,动态代理的实现方式有很多种,比如:JDK 动态代理CGLIB 动态代理Javassit 动态代理

2.1 JDK 动态代理

先来看下 JDK 动态代理机制的使用步骤:

  1. 定义一个接口(Subject)
  2. 创建一个委托类(Real Subject)实现这个接口
  3. 创建一个处理类并实现 InvocationHandler 接口,重写其 invoke 方法(在 invoke 方法中利用反射机制调用委托类的方法,并自定义一些处理逻辑),并将委托类注入处理类
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

该方法有下面三个参数:

  1. proxy:代理类对象
  2. method:过它来调用委托类的方法(反射)
  3. args:传给委托类方法的参数列表

创建代理对象(Proxy):通过 Proxy.newProxyInstance() 创建委托类对象的代理对象

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

这个方法需要 3 个参数:

  1. 类加载器 ClassLoader
  2. 委托类实现的接口数组,至少需要传入一个接口进去
  3. 调用的 InvocationHandler 实例处理接口方法(也就是第 3 步我们创建的类的实例)

2.2 代码示例

我们通过一个简单的示例进行学习动态代理,老李乘车去东北被车撞了,对方耍无赖,不想赔钱,老李就跟对方发生了纠纷需要打官司,但是老李不懂法律,所以就花钱找了一位律师,全权由律师代表处理这场官司:

2.1 ILawSuit

public interface ILawSuit {
    //准备材料
    void prepare();

    //提起诉讼		
    void submit(String proof);

    //法庭辩护
    void defend();
}

2.1.2 老李 LaoLi

public class LaoLi implements ILawSuit {
    @Override
    public void prepare() {
        System.out.println(String.format("老张在准备材料"));
    }

    @Override
    public void submit(String proof) {
        System.out.println(String.format("司机耍无赖,不想赔钱,证据如下:%s",proof));
    }

    @Override
    public void defend() {
        System.out.println(String.format("铁证如山,赔钱"));
    }
}

2.1.3 代理律师 DynamicProxyLawyer

public class DynamicProxyLawyer implements InvocationHandler {

    //被代理的对象
    private Object target;

    public DynamicProxyLawyer(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("案件进展:"+method.getName());
        Object result=method.invoke(target,args);
        return result;
    }
}

2.1.4 ProxyFactory

public class ProxyFactory {
    public static Object getDynProxy(Object target) {
        InvocationHandler handler = new DynamicProxyLawyer(target);
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
    }
}

2.1.5 Client

public class Client {

    public static void main(String[] args) {
        ILawSuit proxy = (ILawSuit) ProxyFactory.getDynProxy(new LaoLi());
        proxy.prepare();
        proxy.submit("摄像头视频拍摄");
        proxy.defend();
    }
}

2.1.6 结果打印

案件进展:prepare
老李在准备材料

案件进展:submit
司机耍无赖,不想赔钱,证据如下:摄像头视频拍摄

案件进展:defend
铁证如山,赔钱

*****************

3 区别与总结

3.1 静态代理和动态代理的区别

  1. 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
  2. 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
  3. 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。