设计模式(1-1)-代理模式

几个月没写博客了。前些日子换了工作,把事情调整好了,又可以继续写博客了~

学习了下代理模式,本篇文章讲动态代理与静态代理模式怎么写,后续文章会讲动态代理(JDK动态代理)原理,讲讲怎么使用CGLIB实现没有接口的类的动态代理

一、代理在生活中的例子

代理, 代表授权方处理事务(wiki 的解释)。 举个例,顾客(client)想买一个u盘; 顾客想去西部数据工厂买一个u盘, 保安都直接把他拦住叫他去京东买; 没办法只能去被授权了的京东那去买,被可恶的中间商赚了差价。
虽然京东和西部数据都是卖u盘的,但是京东只是个代理商,最终需要的u盘还是要去西部数据进货,京东但是可以发优惠券在顾客买u盘前。
生活中的代理例子很多, 找中介租房子。自己不想做的事,可以交给代理方做,但是代理方也能力有限,最终的事还是需要交给本人去做。

代理模式有啥用?

  • 方法增强, 代理方不仅仅是卖u盘了,还可以发放优惠券,赚取差价
  • 控制访问,顾客去找西部数据工厂买个u盘,保安直接把他拦住了

二、 静态代理模式实现

下面我们先看看静态代理在Java中的实现

以买卖u盘为例子,

步骤一,定义一个接口(serviceInterface), 定义厂家与代理商的共同目标(卖u盘)

public interface UpanSell {

    float sell(int amount);
}

步骤二,创建厂家类(serive),实现步骤一定义的接口

public class WesternDigitalUpanFactory implements UpanSell {

    @Override
    public float sell(int amount) {

        float price = 85.0f * amount;

        System.out.println("------西部数据卖出" + amount + "个u盘, 价格: " + price);

        return price;
    }
}

步骤三,创建一个商家类(proxy),实现相同的接口

public class jd implements UpanSell {

    private final UpanSell upanSell = new WesternDigitalUpanFactory();

    @Override
    public float sell(int amount) {

        float price = upanSell.sell(amount);

        // -------增强功能 ↓↓↓↓↓↓↓↓↓↓

        // 中间商赚差价
        price += 25.f * amount;

        System.out.println("------京东卖出" + amount + "个u盘, 价格: " + price);
        System.out.println("------给你一个优惠券, 0.01分");

        return price;
    }
}

步骤四, 创建一个客户端(client)调用商家买U盘

public class Shop {
    public static void main(String[] args) {
        UpanSell upanSell = new jd();

        upanSell.sell(2);
    }
}

输出结果

------西部数据卖出2个u盘, 价格: 170.0
------京东卖出2个u盘, 价格: 220.0
------给你一个优惠券, 0.01分

上面的代理类(jd), 做了两件事,1. 中间商加了价格 2. 给了我们一张优惠券

我们能看到静态代理的实现还是挺简单的,也是多态的一个运用,我们是由商家类调用到的厂家类。
简单是简单,但是

  1. 如果多一个商家,比如淘宝,也要卖了,是不是需要多一个代理类。
  2. 如果serviceInterface,是不是厂家类与商家类都要修改。

那么,我们来看看动态代理是怎么把这些缺点规避掉的

运用JDK实现动态代理👇

三、动态代理模式的实现

动态代理,其实就是不用我们手动实现代理类,而是根据我们传入的参数在程序运行时,动态生成代理类

3.1 JDK动态代理

我们在看JDK动态代理的代码前,不要问为什么要这么写,因为我们可以把它理解成一个模板,规定就是要这么写(不用背,写多了,就自然记住了)。

步骤一、二都跟上面一样

步骤一,定义一个接口(serviceInterface), 定义厂家与代理商的共同目标(卖u盘)

public interface UpanSell {

    float sell(int amount);
}

步骤二,创建厂家类(serive),实现步骤一定义的接口

public class WesternDigitalUpanFactory implements UpanSell {

    @Override
    public float sell(int amount) {

        float price = 85.0f * amount;

        System.out.println("------西部数据卖出" + amount + "个u盘, 价格: " + price);

        return price;
    }
}

步骤三,实现InvocationHandler接口中的invoke() (定义代理类要干的事情) !!!

public class MySellHandler implements InvocationHandler {

    /**
     * 目标对象
     */
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 执行目标方法
        Object res = method.invoke(target, args);

        int amount = (int) args[0];

        if(res != null)
        {
            float price = (Float) res;
            price += 25.f * amount;
            System.out.println("------京东卖出" + amount + "个u盘, 价格: " + price);
            System.out.println("------给你一个优惠券, 0.01分");
            res = price;
        }

        return res;
    }
}

步骤四,获取代理实例,购买U盘 !!!

public class Shop {
    public static void main(String[] args) {

        // 1. 创建目标对象
        UpanSell factory = new WesternDigitalUpanFactory();

        // 2. 创建InvocationHandler对象
        InvocationHandler handler = new MySellHandler(factory);

        // 3. 生成一个代理实体类
        UpanSell proxy = (UpanSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                handler
        );

        float price = proxy.sell(2);
    }
}

输出结果

------西部数据卖出2个u盘, 价格: 170.0
------京东卖出2个u盘, 价格: 220.0
------给你一个优惠券, 0.01分

输出结果当然是一样的。步骤三、步骤四是最重要的,可以依葫芦画瓢,写一个新的JDK动态代理(中介租房…),不用在意细节!

我们能看见,如果增加或减少serviceInterface的方法,修改的地方没有静态代理的多;
用起来更加灵活,代理类与service是解耦的。唯一恼火,可能就是不太容易理解

我们可以看到,我们并没有写代理的类,却实现了代理功能,如何看到这个生成的代理类?

在main方法的第一行加下面的属性

...
   // 加一个这个玩意儿,就可以了
   System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

...

生成的代理class文件

package com.sun.proxy;

import com.ukyu.dynamicproxy.service.UpanSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

    public $Proxy0(InvocationHandler var1) throws  {
        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 float sell(int var1) throws  {
        try {
            return (Float)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.ukyu.dynamicproxy.service.UpanSell").getMethod("sell", Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

先对这个class文件混个眼熟吧,下一篇文章开始去了解JDK动态代理的原理。