设计模式 - 代理模式

101 阅读6分钟

设计模式 - 代理模式

一、引入

当你买火车票时,你通常会通过网络或者售票窗口进行购买,而不是直接去火车站找列车。在这个过程中,售票窗口或者网站就像是一个代理,它帮你与列车取得联系,最终完成了购票的任务。

同样地,代理模式就是为了解决类似的问题。它引入了一个代理对象,帮助你访问某个对象,同时可以在访问前后执行一些额外的操作。这个代理对象担当了一个中介的角色,让你可以通过它来操作真正的对象,而无需直接与真正的对象打交道。

举个简单例子,假设你想下载一个大文件,但你担心下载过程中网络断开,所以你找了一个下载管理器来帮你下载。这个下载管理器就是一个代理,它会在下载前检查网络状态、控制下载速度等操作,然后再帮你实际下载文件。

代理模式就是这样,它为你提供了一个中间层,使得你可以通过代理来操作某个对象,同时可以在操作前后执行一些额外的操作,这样就可以保证原始对象的安全性和灵活性。

二、概念

代理模式(Proxy Pattern)是一种结构型设计模式,它允许一个对象(代理对象)作为另一个对象(真实对象)的接口,以控制对该对象的访问。

代理模式可以在不改变真实对象的情况下,提供对其的间接访问。这种访问的方式可以用于很多用途,比如控制对对象的访问权限、在访问前后执行一些额外的操作等。

静态代理、动态代理、CGLIB代理

三、基本结构

  1. 接口(Subject):定义了真实对象和代理对象之间的共同接口,以便代理对象可以用来代理真实对象。
  2. 真实对象(Real Subject):是被代理的对象,也是代理对象所代表的对象。代理对象将请求传递给真实对象来执行实际的操作。
  3. 代理对象(Proxy):实现了接口,并包含了对真实对象的引用。代理对象通常在其内部包含了一个实例变量,该变量引用了真实对象,以便可以在适当的时候将请求传递给真实对象。

四、示例代码

1、静态代理

/**
* 接口
*/
public interface Image {
    void display();
}
/**
 * 真实对象
 */
public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }
}
/**
 * 代理对象
 */
public class ProxyImage implements Image {
    private String fileName;
    private RealImage realImage;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {
        if (fileName.contains("zf")) {
            if (realImage == null) {
                realImage = new RealImage(fileName);
            }
            realImage.display();
        }
    }
}
/**
* 客户端
*/
public class Client {
    public static void main(String[] args) {
        Image image = new ProxyImage("hazfha");
        // 图像将从磁盘加载
        image.display();

        // 图像不会从磁盘加载
        image.display();
    }
}

2、动态代理

//接口
public interface Subject {
    void doSomething();
}

/**
 * 真实对象
 */
public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("do something");
    }
}
/**
 * 动态代理 : 实现InvocationHandler接口,接口中包含了一个 invoke 方法,用于在代理对象调用方法时进行逻辑处理。通常我们会自定义   	* 一个类实现这个接口,并在 invoke 方法中编写处理逻辑。
 */
public class DynamicProxy implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理前");
        Object invoke = method.invoke(target, args);
        System.out.println("代理后");
        return invoke;
    }
}
/**
 * 动态代理
 */
public class Client {
    public static void main(String[] args) {
        // 创建真实主题
        Subject realSubject = new RealSubject();
        // 使用真实主题创建代理主题

        //loader:类加载器,用于加载代理类。
        //interfaces:代理类要实现的接口。
        //h:实现 InvocationHandler 接口的对象,用于处理方法调用。
      	// Proxy 类:这是 Java 提供的用于创建动态代理对象的类。 
      	//它的 newProxyInstance 方法可以接受一个类加载器、一组接口和一个 InvocationHandler 对象,然后返回一个代理对象。
        Subject proxySubject = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                new DynamicProxy(realSubject));
        // 调用代理主题的方法
        proxySubject.doSomething();
    }
}

3、CGLIB代理

/**
 * 接口
 */
public interface Subject {
    void doSomething();
}
/**
 * 真实对象
 */
public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("do something");
    }
}
/**
 * CGLIB动态代理 : 实现MethodInterceptor接口
 */
public class DynamicProxy implements MethodInterceptor {
    private Object target;

    public DynamicProxy(Object object) {
        this.target = object;
    }

  
  	//obj:表示代理对象。
		//method:表示被调用的方法。
		//args:表示方法的参数数组。
		//proxy:表示方法的代理。
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理前");
        Object invoke = method.invoke(target, objects);
        System.out.println("代理后");
        return invoke;
    }
}
/**
* 客户端
*/
public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
				//创建一个 Enhancer 对象,用于生成代理类。
        Enhancer enhancer = new Enhancer();
        // 设置代理类的父类,也就是目标类
        enhancer.setSuperclass(RealSubject.class);
        // 设置回调函数,即在代理类调用方法时,会调用回调函数中的方法
        enhancer.setCallback(new DynamicProxy(realSubject));

        // 通过 enhancer.create() 方法生成代理对象。
        RealSubject realSubject1 = (RealSubject) enhancer.create();
        // 调用代理对象的方法
        realSubject1.doSomething();
    }
}

五、用途

  1. 控制访问权限:代理可以作为一个门卫,控制对真实对象的访问权限。只有符合一定条件的请求才会被代理对象转发给真实对象。
  2. 延迟加载:代理可以在需要时才实例化真实对象,从而实现延迟加载。这在需要消耗大量资源的情况下特别有用。
  3. 实现缓存:代理可以缓存对真实对象的请求的结果,当相同的请求再次发生时,代理可以直接返回缓存的结果,而不必再次调用真实对象。
  4. 实现日志记录:代理可以在调用真实对象的方法之前或之后记录日志,从而实现日志记录功能。
  5. 实现安全检查:代理可以在调用真实对象的方法之前进行安全检查,确保调用者有权执行该操作。
  6. 实现事务管理:代理可以在调用真实对象的方法之前开启事务,在方法执行后提交或回滚事务。
  7. 实现远程代理:代理可以在不同的地址空间中代表真实对象,实现远程调用。
  8. 实现虚拟代理:代理可以在真实对象加载过程中表现得像真实对象一样,直到需要真正的结果。
  9. 实现保护代理:代理可以检查调用者的权限,以确保它们具有执行操作的权限。
  10. 实现动态代理:可以在运行时动态创建代理类,而不需要静态定义代理类。

六、总结

优点:

  1. 控制访问:代理模式可以在真实主题类的方法执行前后,进行一些控制,比如权限控制、验证等。
  2. 保护隐私:代理模式可以隐藏真实主题的实现细节,保护了真实主题的隐私。
  3. 延迟加载:虚拟代理可以延迟加载真实主题,节省资源开销。比如在创建代理对象时,不会立即创建真实对象,而是在需要时才创建。
  4. 简化调用:代理模式可以为真实主题提供一个简化的接口,客户端可以直接调用代理,而无需关心真实主题的复杂性。

缺点:

  1. 增加复杂性:引入了代理类,可能会增加系统的复杂性。
  2. 性能损耗:在访问真实主题之前或之后,代理可能会执行一些额外的操作,导致性能损耗。