JAVA 设计模式之《代理模式》

一、代理模式介绍

代理模式,非常容易理解,一眼看过去大家都懂,字面意思,如果不懂稍微琢磨琢磨就懂了,我在这里举几个栗子🌰 ,以便更容易理解。

贾乃亮作为一个明星,为了更专注于自己的工作,他找了一个经纪人,帮他约综艺、约电视剧、约电影等等。
英雄联盟打的太菜了,但是为了冲到更高的段位,在别人面前装装13,于是找了一个代练帮忙上分。
背井离乡,来到一个陌生的城市打拼,不知道住在哪里怎么办?我们可以通过中介更快的找到心仪的住所。
春运的票实在太难抢,我们只能使用抢票软件帮我们抢票。
可以看到,在生活中,代理无处不在,无论是经纪人、游戏代练、房产中介、抢票软件,都是帮我们做事,对我们的能力进行了扩展。

那么在Java中使用代理模式,通过代理对象访问目标对象,从而在不改变目标对象原有代码的基础上,进行功能的扩展和增强。

目前在Java中分为三种代理模式:静态代理、jdk动态代理、cglib动态代理。

二、静态代理

静态代理,需要代理对象和目标对象实现同一个接口。

现在市面上的游戏很多,我们创建一个接口,写上现在一直很火的一款游戏,英雄联盟

public interface Play {
    public void lol();
}

有一个对象,他叫 “有趣的灵魂200斤”,是个肥宅,名如其人对吧,他有一个爱好,就是玩、娱乐,我们让肥宅小兄弟实现一下这个接口,看看他是怎么玩这个游戏的。

public class FeiZhai implements Play {

    public void lol() {
        System.out.println("B键已扣,不死不休!");
    }
}

有一天我们的肥宅小兄弟,刚开了新的一局英雄联盟排位赛,突然肚子一阵作响,他看了看时间,于是拿起手机打开饿不饿APP订了个餐,然后继续游戏。
那么我们的肥宅小兄弟为什么在网上订餐呢,因为他不会做饭,所以只能在网上订餐,而且商家做好餐,由我们的外卖小哥送过来,就可以吃了,很方便,主要是不耽误玩游戏。

/**
 * 饿不饿App-肥宅小兄弟
 */
public class EbueProxy implements Play {

    //声明目标对象-为肥宅小兄弟服务
    private FeiZhai feiZhai;

    //通过构造方法,获取目标对象,进行赋值
    public EbueProxy(Play play) {
        this.feiZhai = (FeiZhai) play;
    }

    //对原来的方法进行扩展
    public void lol() {
        System.out.println("商家接到订单,开始做饭");
        System.out.println("饭做完了,外卖小哥取到餐");

        //调用目标对象的方法
	//在不影响肥宅小兄弟玩LOL的情况下,进行功能的拓展和增强。
        feiZhai.lol();
        System.out.println("外卖小哥进行配送");
        System.out.println("叮咚!您好,您的餐,请慢用!");
    }
}

我们的肥宅小兄弟,为了不耽误自己玩游戏,委托饿不饿APP送餐上门,这就是一种代理模式,让我们运行看一下结果。

public class main {

    public static void main(String[] args) {

        Play feiZhai = new FeiZhai();

        EbueProxy ebueProxy = new EbueProxy(feiZhai);

        ebueProxy.lol();
    }
}
商家接到订单,开始做饭
饭做完了,外卖小哥取到餐
B键已扣,不死不休!
外卖小哥进行配送
叮咚!您好,您的餐,请慢用!

三、jdk 动态代理

又是充满希望的一天,今天我们的肥宅小兄弟不想玩LOL了,他熟练的拿起手机,打开和平精英,准备吃(kuai)鸡(di)之旅。

我们给接口类加一个方法

public interface Play {
    public void lol();

    public void chiJi();
}

再具体实现一下

public class FeiZhai implements Play {

    public void lol() {
        System.out.println("B键已扣,不死不休!");
    }

    public void chiJi() {
        System.out.println("人体描边说的就是在下了!");
    }
}

到饭点了,又是和昨天一样的情况,我们再写一套代码,委托饿不饿APP帮我们送下餐

/**
 * 饿不饿App
 */
public class EbueProxy implements Play {

    //声明目标对象-为肥宅小兄弟服务
    private FeiZhai feiZhai;

    //通过构造方法,获取目标对象,进行赋值
    public EbueProxy(Play play){
        this.feiZhai = (FeiZhai) play;
    }

    //对原来的方法进行扩展
    public void lol() {
        System.out.println("商家接到订单,开始做饭");
        System.out.println("饭做完了,外卖小哥取到餐");

	//调用目标对象的方法
	//在不影响肥宅小兄弟玩LOL的情况下,进行功能的拓展和增强。
        feiZhai.lol();
        System.out.println("外卖小哥进行配送");
        System.out.println("叮咚!您好,您的餐,请慢用!");
    }

    //对原来的方法进行扩展
    public void chiJi() {
        System.out.println("商家接到订单,开始做饭");
        System.out.println("饭做完了,外卖小哥取到餐");

	//调用目标对象的方法
	//在不影响肥宅小兄弟玩吃鸡的情况下,进行功能的拓展和增强。
        feiZhai.chiJi();
        System.out.println("外卖小哥进行配送");
        System.out.println("叮咚!您好,您的餐,请慢用!");
    }
}

看到这里的小伙伴们,应该都发现问题了,如果肥宅小兄弟每天换个游戏,那我们岂不是要累死了,而且现在还只是1个肥宅小兄弟,如果是2个、3个、100个呢,难道我们要创建不同的 EbueProxy 代理类么?

要知道现在咱们代码中的 EbueProxy 类,只为 FeiZhai 这个类提供代理服务,相当于定制版,如果我想为FeiZhai1FeiZhai2提供代理服务,我还要再写2个代理类,如果这个时候Play接口新加了几个方法,我们又要去维护实现了Play接口的这些类,很不方便。

本着最少的代码干最多的事这种原则,我们不能接受这样的结果。

这个时候我们就要使用 jdk动态代理 了,我们创建一个类 EbueDynamicProxy,实现 InvocationHandler 接口,重写 invoke 方法。

/**
 * 饿不饿App-动态代理
 */
public class EbueDynamicProxy implements InvocationHandler {

    //注意这里,我们换成了Object,意味着我们可以接受不同的目标对象
    private Object object;

    //通过构造方法,获取目标对象,进行赋值
    public EbueDynamicProxy(Object object){
        this.object = object;
    }

    /**
     *
     * 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
     * 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
     * InvocationHandler这个接口的唯一一个方法 invoke 方法:
     * 该方法接收3个参数:
     * proxy:  指代我们所代理的那个目标对象
     * method: 指代的是我们所要调用目标对象的某个方法的Method对象
     * args:  指代的是调用目标对象某个方法时接受的参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("商家接到订单,开始做饭");
        System.out.println("饭做完了,外卖小哥取到餐");

        Object obj = method.invoke(object, args);

        System.out.println("外卖小哥进行配送");
        System.out.println("叮咚!您好,肥先生的餐,请慢用!\n");
        return obj;
    }
}

静态代理和jdk动态代理最大的区别就是,jdk动态代理是jvm帮我们生成了代理类。

至于jvm怎么生成的我们目前不需要去操心,我们看看我们怎么触发去让jvm生成。

public class main {

    public static void main(String[] args) {

        //创建一个目标对象的实例
        Play feizhai = new FeiZhai();

        //创建一个与目标对象相关联的InvocationHandler
        InvocationHandler ebueDynamicProxy = new EbueDynamicProxy(feizhai);

        //获取目标对象的类加载器
        ClassLoader classLoader = feizhai.getClass().getClassLoader();

        //获取目标对象的所有接口
        Class[] interfaces = feizhai.getClass().getInterfaces();

        //Proxy.newProxyInstance:返回代理类的一个实例,返回后的代理类可以当作被代理类使用
        Play feiZhaiProxy = (Play) Proxy.newProxyInstance(classLoader, interfaces, ebueDynamicProxy);

        //英雄联盟
        feiZhaiProxy.lol();

        //吃鸡
        feiZhaiProxy.chiJi();

    }
}

运行一下看看输出

商家接到订单,开始做饭
饭做完了,外卖小哥取到餐
B键已扣,不死不休!
外卖小哥进行配送
叮咚!您好,您的餐,请慢用!

商家接到订单,开始做饭
饭做完了,外卖小哥取到餐
人体描边说的就是在下了!
外卖小哥进行配送
叮咚!您好,您的餐,请慢用!

是不是很简单,接下来我们分析一下原理,知道怎么做还不够,还得知道为什么能这么做。

通过静态代理,我们很明显可以看到,动态代理是由静态代理演化而来,之前我们手写代理类,有几个目标对象就写几个代理类,目标对象里有几个方法,代理类里就写几个方法,现在换成jdk动态代理,我们就靠jvm来动态生成代理类,我们只要需要实现 InvocationHandler 接口,重写 invoke 方法,添加上需要扩展的功能代码,然后交给jvm就可以了,jvm会帮我们在程序运行时生成代理类,代理类的名称为$Proxy0、$Proxy1、$Proxy2 …以此类推。

这个时候我们想看看jvm生成的代理类是什么样的怎么办?我们在main方法里面加上这样一行代码,这样jvm生成的代理类就会出现在项目里的com.sun.proxy这个目录下面了。

public class main {

    public static void main(String[] args) {

        //这行代码的意思是将JDK动态代理生成的class文件保存到本地
        //作者本地jdk1.8亲测可用,有人说新版本的jdk用下面一行好使,不知道是多新的版本,如果有人用了发现没生成,可以换下面这行代码试试!
        //System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        //创建一个目标对象的实例
        Play feizhai = new FeiZhai();

        //创建一个与目标对象相关联的InvocationHandler
        InvocationHandler ebueDynamicProxy = new EbueDynamicProxy(feizhai);

        //获取目标对象的类加载器
        ClassLoader classLoader = feizhai.getClass().getClassLoader();

        //获取目标对象的所有接口
        Class[] interfaces = feizhai.getClass().getInterfaces();

        //Proxy.newProxyInstance:返回代理类的一个实例,返回后的代理类可以当作被代理类使用
        Play feiZhaiProxy = (Play) Proxy.newProxyInstance(classLoader, interfaces, ebueDynamicProxy);

        //英雄联盟
        feiZhaiProxy.lol();

        //吃鸡
        feiZhaiProxy.chiJi();

    }
}

我们运行一下,果然生成了,文件名也是$Proxy开头

在这里插入图片描述

我们再看看class文件里面的代码是什么样的

package com.sun.proxy;

import com.test.proxy.Play;
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 Play {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;
    private static Method m4;

    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 void chiJi() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    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.test.proxy.Play").getMethod("chiJi");
            m4 = Class.forName("com.test.proxy.Play").getMethod("lol");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我们看到在这个类里面,写了一个静态代码块,这里的m1、m2、m3等等,都是使用了反射找到了对应的Method对象。

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.test.proxy.Play").getMethod("lol");
        m4 = Class.forName("com.test.proxy.Play").getMethod("chiJi");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(var3.getMessage());
    }
}

然后我们再找到我们写的lol方法,我们拿这个进行举例说明。

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

可以看到代码很简单,主要就是这么一行代码
super.h.invoke(this, m3, (Object[])null);
这行代码的意思就是调用当前类的父类的h变量的invoke方法。这么读发现有点绕是不是,没事,我们来剖析一下看看当前类的父类是谁?当前类的父类的h变量又是什么?调用这个invoke方法又有什么用?来上代码。

import com.test.proxy.Play;
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 Play  
从上面这段代码我们可以看到,$Proxy0代理类继承了Proxy类,并且实现了我们写的Play接口,这下明白了,当前类的父类就是Proxy类。


public class Proxy implements java.io.Serializable {

    private static final long serialVersionUID = -2222568056686623797L;

    private static final Class<?>[] constructorParams = { InvocationHandler.class };

    private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

    //看这里
    protected InvocationHandler h;
  
}

再看上面这段代码,h变量就是InvocationHandler类,那么super.h.invoke方法,就相当于是调用了proxy的h变量的invoke方法,还记得invoke方法是什么吗,就是我们写的EbueDynamicProxy类里面的invoke方法,执行的就是这个方法。至于什么时候把EbueDynamicProxy类赋值给了h变量,我们在main方法里不是写了Proxy.newProxyInstance了,就是在那个时候,把EbueDynamicProxy传过去的。

接下来我们来粗略看一下cglib动态代理。

如果你使用了Spring框架,就不用单独引入cglib了,因为Spring集成了cglib。

接下来我们创建一个普通的类,写上一个方法。

public class HelloFeiZhai {

    public void hello(){
        System.out.println("你好,肥宅!");
    }

}

然后我们开始为这个类创建动态代理,首先要创建一个类,实现MethodInterceptor接口,并且重写intercept方法。

public class CglibDynamicProxy implements MethodInterceptor {

    /**
     * 增强代码
     * @param object        被代理的对象
     * @param method        代理的方法
     * @param objects       方法参数
     * @param methodProxy   cglib方法代理对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println("你好,我叫 有趣的灵魂200斤");

        Object obj = methodProxy.invokeSuper(object, objects);

        System.out.println("我好你******");

        return obj;
    }
}
然后看看main方法


public static void main(String[] args) {

    CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy();

    //Enhancer是cglib的字节码增强器,用来创建动态代理类
    Enhancer enhancer = new Enhancer();

    //指定要代理的业务类(即:为要生成的代理类指定父类)
    enhancer.setSuperclass(HelloFeiZhai.class);

    //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现MethodInterceptor接口的intercept()方法进行拦截
    enhancer.setCallback(cglibDynamicProxy);

    // 创建动态代理类对象并返回
    HelloFeiZhai helloFeiZhai = (HelloFeiZhai)enhancer.create();

    // 调用
    helloFeiZhai.hello();

}

运行结果:

你好,我叫 有趣的灵魂200斤
你好,肥宅!
我好你*****

四、cglib 动态代理

cglib的源码确实比较复杂,我自己还有好多地方不明白,就不拿出来误导大家了,我粗略的说一下原理:通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

五、总结

jdk动态代理在老的java版本中,效率不尽人意,是比cglib要慢的,但是在jdk1.8版本,据大佬说,jdk动态代理已经是比cglib要快了。

在这篇文章中我们讲了java设计模式之一的《代理模式》,介绍了现在java中的三种代理模式(静态代理、jdk动态代理、cglib动态代理),而且分别演示了三种代理模式的使用方法、说明了它们之间的关系和区别,以及优缺点。

如果写的有什么问题,欢迎大家在底下评论指正,我会及时采纳和修改,感谢!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嗷嗷待哺丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值