0%

设计模式之结构型模式(七):代理模式

1. 代理模式概述

  • 概念
    • 给某一个对象提供一个代理,并由代理对象控制对原对象的引用
    • 代理模式分为静态代理动态代理,二者没有本质区别,只是换了一种写法
    • 使用动态代理,需要把一个类传入,然后根据它正在调用的方法名判断是否需要加以控制

2. 静态代理模式 Demo

  • 新建网络请求接口

    1
    2
    3
    4
    5
    public interface IHttp {
    void request(String sendData);

    void onSuccess(String receivedData);
    }
  • 新建 Http 请求工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class HttpUtil implements IHttp {
    @Override
    public void request(String sendData) {
    System.out.println("网络请求中...");
    }

    @Override
    public void onSuccess(String receivedData) {
    System.out.println("网络请求完成。");
    }
    }
  • 新建 Http 代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class HttpProxy implements IHttp {
    private final HttpUtil httpUtil;

    public HttpProxy(HttpUtil httpUtil) {
    this.httpUtil = httpUtil;
    }

    @Override
    public void request(String sendData) {
    httpUtil.request(sendData);
    }

    @Override
    public void onSuccess(String receivedData) {
    httpUtil.onSuccess(receivedData);
    }
    }
  • 在代理类中增加打印日志信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class HttpProxy implements IHttp {
    private final HttpUtil httpUtil;

    public HttpProxy(HttpUtil httpUtil) {
    this.httpUtil = httpUtil;
    }

    @Override
    public void request(String sendData) {
    System.out.println("发送数据:" + sendData);
    httpUtil.request(sendData);
    }

    @Override
    public void onSuccess(String receivedData) {
    System.out.println("收到数据:" + receivedData);
    httpUtil.onSuccess(receivedData);
    }
    }
  • 客户端验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Client {
    @Test
    public void test() {
    HttpUtil httpUtil = new HttpUtil();
    HttpProxy proxy = new HttpProxy(httpUtil);
    proxy.request("request data");
    proxy.onSuccess("received result");
    }
    }
  • 运行程序,输出如下

    1
    2
    3
    4
    发送数据:request data
    网络请求中...
    收到数据:received result
    网络请求完成。
  • 分析

    • 除了打印日志,还可以用来做权限管理。看起来代理模式和装饰模式一样,但两者的目的不同
    • 装饰模式是为了增强功能或添加功能;代理模式主要是为了加以控制

3. 动态代理模式 Demo

  • 伪代码:实现起来难点就是怎么让 HttpUtil 调用任意方法时,都通过一个方法间接调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class HttpProxy {
    private final HttpUtil httpUtil;

    public HttpProxy(HttpUtil httpUtil) {
    this.httpUtil = httpUtil;
    }

    // 假设调用 httpUtil 的任意方法时,都要通过这个方法间接调用, methodName 表示方法名,args 表示方法中传入的参数
    public visit(String methodName, Object[] args) {
    if (methodName.equals("request")) {
    // 如果方法名是 request,打印日志,并调用 request 方法,args 的第一个值就是传入的参数
    System.out.println("发送数据:" + args[0]);
    httpUtil.request(args[0].toString());
    } else if (methodName.equals("onSuccess")) {
    // 如果方法名是 onSuccess,打印日志,并调用 onSuccess 方法,args 的第一个值就是传入的参数
    System.out.println("收到数据:" + args[0]);
    httpUtil.onSuccess(args[0].toString());
    }
    }
    }
  • 实际的动态代理类:使用反射技术

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class HttpProxy implements InvocationHandler {
    private HttpUtil httpUtil;

    public IHttp getInstance(HttpUtil httpUtil) {
    this.httpUtil = httpUtil;
    return (IHttp) Proxy.newProxyInstance(httpUtil.getClass().getClassLoader(), httpUtil.getClass().getInterfaces(), this);
    }

    // 调用 httpUtil 的任意方法时,都要通过这个方法调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result = null;
    if (method.getName().equals("request")) {
    // 如果方法名是 request,打印日志,并调用 request 方法
    System.out.println("发送数据:" + args[0]);
    result = method.invoke(httpUtil, args);
    } else if (method.getName().equals("onSuccess")) {
    // 如果方法名是 onSuccess,打印日志,并调用 onSuccess 方法
    System.out.println("收到数据:" + args[0]);
    result = method.invoke(httpUtil, args);
    }
    return result;
    }
    }
    • getInstance() 方法中,Proxy.newProxyInstance() 方法是 Java 系统提供的方法,专门用于动态代理。其中传入的第一个参数是被代理类的 ClassLoader,第二个参数是被代理类的 Interfaces,这两个参数都是 Object 中的,每个类都有,这里就是固定写法。只要知道系统需要这两个参数才能让我们实现我们的目的:调用被代理类的任意方法时,都通过一个方法间接调用。现在我们给系统提供了这两个参数,系统就会在第三个参数中帮我们实现这个目的
    • 第三个参数是 InvocationHandler 接口,这个接口中只有一个方法:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;。现在我们调用被代理类 HttpUtil 的任意方法时,都会通过这个 invoke() 方法调用了。invoke() 方法中,第一个参数暂时用不上,第二个参数 method 就是调用的方法,使用 method.getName() 可以获取到方法名,第三个参数是调用 method 方法需要传入的参数。本例中无论 request() 还是 onSuccess() 都只有一个 String 类型的参数,对应到这里就是 args[0]。返回的 Object 是 method 方法的返回值,本例中都是无返回值的。这里的 method.invoke() 就是反射调用方法
  • 修改客户端验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Client {
    @Test
    public void test() {
    HttpUtil httpUtil = new HttpUtil();
    IHttp proxy = new HttpProxy().getInstance(httpUtil);
    proxy.request("request data");
    proxy.onSuccess("received result");
    }
    }
  • 运行程序,输出与之前一样

    1
    2
    3
    4
    发送数据:request data
    网络请求中...
    收到数据:received result
    网络请求完成。

4. 静态代理和动态代理的区别

  • 静态代理和动态代理没有本质区别,动态代理的好处是节省代码量
  • 例如被代理类有 20 个方法,我们只需要控制其中的两个方法,就可以用动态代理通过方法名对被代理类进行动态控制;如果使用静态代理,就需要将另外 18 个方法也写出来,非常繁琐
-------------------- 本文结束感谢您的阅读 --------------------