设计模式 六月 03, 2021

代理模式

文章字数 15k 阅读约需 14 mins. 阅读次数 0

代理模式

解决问题

​ 在不直接操作对象的情况下,对此对象进行访问.

方案

​ 可以通过引入一个新的对象,来实现对真实对象的操作或者将新的对象作为真实对象的一个替身。即代理对象。它可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务.

代理可以提供延迟实例化(lazy instantiation),控制访问, 等等,包括只在调用中传递。

​ 一个处理纯本地资源的代理有时被称作虚拟代理。

​ 远程服务的代理常常称为远程代理。

​ 强制 控制访问的代理称为保护代理。

结构

代理模式

  • 代理角色(Proxy):

    • 保存一个引用使得代理可以访问实体。若 RealSubject和Subject的接口相同,Proxy会引用Subject。
    • 提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
    • 控制对实体的存取,并可能负责创建和删除它。
    • 其他功能依赖于代理的类型:
    • Remote Proxy负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编 码的请求。
    • Virtual Proxy可以缓存实体的附加信息,以便延迟对它的访问。
    • Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。
  • 抽象主题角色(Subject):定义真实主题角色RealSubject 和 抽象主题角色Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。代理主题通过持有真实主题RealSubject的引用,不但可以控制真实主题RealSubject的创建或删除,可以在真实主题RealSubject被调用前进行拦截,或在调用后进行某些操作.

  • 真实主题角色(RealSubject):定义了代理角色(proxy)所代表的具体对象.

适用性

  • 远程代理(Remote Proxy)为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)
  • 虚拟代理(Virtual Proxy)根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • 保护代理(Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
  • 智能指引(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。
  • Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

优缺点

优点

  • 代理模式能够 协调调用者和被调用者 ,在一定程度上降低了系统的耦合度。
  • Remote Proxy可以隐藏一个对象存在于不同地址空间的事实,即客户端可以访问在远程机器上的对象
  • 允许在访问一个对象时有一些附加的内务处理

缺点

  • 代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

经典实现

静态代理

/**
*    抽象主题(Subject):牛奶厂家买牛奶
*/
interface Milk{
    void sell(int number);
}

/**
* 真实主题(RealSubject): 牛奶制造商 伊利 买牛奶
*/
class Manufacturer implements Milk{
 
    private int number=2000;
    private String name;
    private int price;
    private  int saledCount=0;
 
    public int getPrice() {
        return price;
    }
    public Manufacturer(){
        name = "牛奶";
        price = 100;
    }
    public void sell(int number) {
        System.out.println("生产商卖  "+number+"  罐奶粉给客户");
        saledCount +=number;
    }
}

/**
* 代理(Proxy): 牛奶代购平台(微商) 买 伊利牌 牛奶
*/
class MilkProxy implements Milk{
 
    //代理有渠道向伊利进货
    Manufacturer manufacturer = new Manufacturer();
    private int price=0;
    private int saledCount=0;
    private long profit=0;
    private int size=0;
 
    public MilkProxy(){
        this.price=200;
    }
 
    @Override
    public void sell(int number) {
        size += number;
        while(size > 20){
        //打开下面的注释这样写表示当你能够靠卖奶粉赚到钱的时候(只买1两件,代理商怎么愿意跑一趟嘛),代理商才真正的去找生产商(可以看成是虚代理)
            //manufacturer = new Manufacturer();
            System.out.println("去拿货");
            // 作为客户向制造商买牛奶
            //实际上他是在帮生产商卖牛奶,然后自己赚差价
            manufacturer.sell(size);
            
            System.out.println("代购卖  " + size + "  罐奶粉给客户");
            saledCount+=size;
            size=0;
        }
    }
    public long computeProfit(){
        profit = saledCount*(price-manufacturer.getPrice());
        return profit;
    }
}

public class Demo{
    
    public static void main(String[] args) {
 
 
        // 但是不是每个人都有空去伊利厂家(这就可以看成是远程代理)
        // 当然伊利也不可以能直接和每个想买奶粉的人打交道,他说我只卖给代理商(这可以看成是保护代理,控制应用对象具有不同的访问权限)
        // 这时候朋友圈里面跳出来来一个奶粉代购妹妹,
        //奶粉原价是100,但是代理价200,代理给你加价了,这就是在一个代理对象前后执行了附加的操作(这可以看成是智能指引代理,他在访问前后执行了附加操作)
        MilkProxy milkProxy = new MilkProxy();
 
        //张三准备卖5罐奶粉
        milkProxy.sell(5);
        //李四买10罐
        milkProxy.sell(10);
        //王五买20罐
        milkProxy.sell(20);
        
        System.out.println("代购收益:"+milkProxy.computeProfit());
    }
}

运用

代理模式在很多情况下都非常有用,特别是你想强行控制一个对象的时候,比如:延迟加载,监视状态变更的方法等等.

1、“增加一层间接层”是软件系统中对许多负责问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段。

2、具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象作细粒度的控制,有些可能对组件模块提供抽象代理层,在架构层次对对象作proxy。

3、proxy并不一定要求保持接口的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。例如上面的那个例子,代理类型ProxyClass和被代理类型LongDistanceClass可以不用继承自同一个接口,正像GoF《设计模式》中说的:为其他对象提供一种代理以控制这个对象的访问。代理类型从某种角度上讲也可以起到控制被代理类型的访问的作用。

代理模式变种 动态代理

分类

JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

jdk动态代理

基于接口的动态代理

关键使用语句

Porxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);

  • ClassLoader: 类加载器,一般使用被代理对象的类加载器即可.

  • interfaces: 要实现的代理接口.

  • InvocationHandler: 调用处理器,被调用对象被执行时的处理.

Proxy 关键代码

// 创建代理类
Class<?> cl = getProxyClass(loader, interfaces);
// 实例化代理对象
Constructor cons = cl.getConstructor(constructorParams);

在getConstructor()方法里最后调用了代理生成器来生成类保存在内存中.

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

而具体要代理的方法生成时调用的asm类库(二进制字节码操作类库),直接修改二进制文件来修改类.

例子改造

/**
 *    抽象主题(Subject):牛奶厂家买牛奶
 */
interface Milk{
    void sell(int number);
}

/**
 * 真实主题(RealSubject): 牛奶制造商 伊利 买牛奶
 */
class Manufacturer implements Milk{

    private int number=2000;
    private String name;
    private int price;
    private  int saledCount=0;

    public int getPrice() {
        return price;
    }
    public Manufacturer(){
        name = "牛奶";
        price = 100;
    }
    @Override
    public void sell(int number) {
        System.out.println("生产商卖  "+number+"  罐奶粉给客户");
        saledCount +=number;
    }
}

/**
 * 代理(Proxy): 牛奶代购平台(微商) 买 伊利牌 牛奶
 */
class MilkProxy implements InvocationHandler {

    //代理渠道向伊利
    Object targetObject;


    private int price=0;
    private int saledCount=0;
    private long profit=0;
    private int size=0;

    public MilkProxy() {
        price = 200;
    }

    public Object newProxy(Object targetObject) {
        // 将目标对象传入进行代理
        this.targetObject = targetObject;
        // 返回代理对象,利用反射获取目标类与目标类的接口
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 进行逻辑处理的函数
        checkPopedom();
        this.size += (Integer) args[0];
        Object ret = null;
        if (size > 20){
            System.out.println("去拿货....");
            args[0] = size;
            // 调用invoke方法
            ret = method.invoke(targetObject, args);
        }
        return ret;
    }
    private void checkPopedom() {
        // 模拟检查权限
        System.out.println("检查权限:checkPopedom()!");
    }
}
public class JdkMilkDemo {
    public static void main(String[] args) {
        // 找到代理商
        MilkProxy milkProxy = new MilkProxy();
        // 告诉牛奶代理商要买伊利厂的牛奶.
        Object o2 = milkProxy.newProxy(new Manufacturer());
        Milk milk2 = (Milk) o2;
        // 张三买了12盒
        milk2.sell(12);
        // 李四买了12盒
        milk2.sell(12);
    }
}

动态代理生成的文件

  1. 通过设置 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

  2. 执行 ProxyGenerator.generateProxyClass(String var0, Class<?>[] var1);方法

    byte[] bs = ProxyGenerator.generateProxyClass("Manufacture",new Class<?>[]{Milk.class});
    new FileOutputStream(new File("Manufacture.class")).write(bs);
    

开启保存生成的代理文件.可以观察代理类.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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

final class $Proxy0 extends Proxy implements Milk {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public $Proxy0(InvocationHandler var1) throws  {
        // 传给了super的h.
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sell(int var1) throws  {
        try {
            // 调用invocationHandler的invoke方法.
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.yingb.pat.proxy.jdkMilk.Milk").getMethod("sell", Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

CGLIB动态代理

CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。

关键点在于创建被代理对象的子类

Enhancer enhancer = new Enhancer();
// 设置被代理类作为父类
enhancer.setSuperclass(Manufacturer.class);
// 设置方法拦截器,里面定义自己的逻辑
enhancer.setCallback(new MilkProxy());
Manufacturer manufacturer = (Manufacturer) enhancer.create();

方法拦截器说明

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
    methodPorxy.invokeSuper(o, objects);
}

object: 被代理对象,

method: 被代理对象方法,

objects: 参数

methodProxy: 生成的代理对象.

注意:底层还是使用的asm.

例子改造

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author: 60512
 * @create: 2021/09/10 13:04
 **/
public class CglibMilk {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Manufacturer.class);
        enhancer.setCallback(new MilkProxy());
        Manufacturer manufacturer = (Manufacturer) enhancer.create();
        manufacturer.sell(10);
        manufacturer.sell(3);
        manufacturer.sell(23);
    }
}


/**
 * 真实主题(RealSubject): 牛奶制造商 伊利 买牛奶
 */
class Manufacturer {

    private int number=2000;
    private String name;
    private int price;
    private  int saledCount=0;

    public int getPrice() {
        return price;
    }
    public Manufacturer(){
        name = "牛奶";
        price = 100;
    }

    public void sell(int number) {
        System.out.println("生产商卖  "+number+"  罐奶粉给客户");
        saledCount +=number;
    }
}


/**
 * 代理(Proxy): 牛奶代购平台(微商) 买 伊利牌 牛奶
 */
class MilkProxy implements MethodInterceptor {

    private int price=0;
    private int saledCount=0;
    private long profit=0;
    private int size=0;

    public MilkProxy() {
        price = 200;
    }

    private void checkPopedom() {
        // 模拟检查权限
        System.out.println("检查权限:checkPopedom()!");
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 进行逻辑处理的函数
        checkPopedom();
        this.size += (Integer) objects[0];
        Object ret = null;
        if (size > 20){
            System.out.println("去拿货....");
            objects[0] = size;
            // 调用invoke方法
            ret = methodProxy.invokeSuper(o, objects);
        }
        return ret;
    }
}
0%