设计模式学习4-代理模式(用沸羊羊的例子弄懂代理模式)及其实现(静态代理、jdk代理和cglib代理的使用及其实现)

文章内容

介绍了代理模式的基本概念、静态代理的代码实现。jdk代理的使用及其实现、cglib代理的使用及其实现。

写在前面

沸羊羊爱上了美羊羊,于是沸羊羊想送花给美羊羊,但是沸羊羊又是个害羞且审美不太行的汉子,所以求喜羊羊帮忙,于是喜羊羊帮沸羊羊买花,找到美羊羊,再送花,最后还要帮沸羊羊美言几句。最后皆大欢喜,喜羊羊和美羊羊在一起了(bushi)。
在上面的故事中,沸羊羊和喜羊羊同时实现了送花的接口,沸羊羊就是被代理的对象,喜羊羊就是代理对象,代理对象接管了被代理对象实现了对被代理对象访问,同时又实现了功能的增强,这就是代理模式。
image.png

定义

image.png
被代理的对象只要完成简单的工程,而代理对象对该功能能进行扩展。

类图

image.png
Subject:被代理和代理同时实现的接口。
RealSubject:访问实体,需要被代理的对象。
Proxy:代理对象,组合了RealSubject,在RealSubject的request方法上进行增强。
代理有静态代理和动态代理两种,下面将详细介绍。

静态代理

首先我们来完成静态代理,就拿之前沸羊羊的例子来实现。
SendFlower对应-Subject接口
FeiSheep-RealSubject
XiSheep-Proxy
在喜羊羊中组合了沸羊羊中,在沸羊羊的的送花前后,增强了功能

/**
 * @version 1.0
 * @author: Pig One
 * @date: 2022/7/28 16:11
 * @decription: 静态代理
 */
public class StaticProxy {
    public static void main(String[] args) {
        XiSheep xiSheep = new XiSheep(new FeiSheep());
        xiSheep.sendflower();
    }
}

class XiSheep implements SendFlower{
    private FeiSheep feiSheep;

    public XiSheep(FeiSheep feiSheep) {
        this.feiSheep = feiSheep;
    }

    @Override
    public void sendflower() {
        System.out.println("买花");
        System.out.println("找到美羊羊");
        feiSheep.sendflower();
        System.out.println("帮忙美言几句");

    }
}

class FeiSheep implements SendFlower{

    @Override
    public void sendflower() {
        System.out.println("给美羊羊送花");
    }
}


interface SendFlower{
    void sendflower();
}


结果如下
image.png
静态代理比较简单,但是试想,如果现在沸羊羊愈加情深,想要送零食,想化妆品等等该怎么办?如果这样的话,需要加很多代码,是不是特别麻烦?于是痴情的沸羊羊有了新的想法,他找了一个机器人,直接给他下命令就可以了!而这个机器人已经在羊村有售,这就是jdk代理和cglib代理!

动态代理

动态代理的著名实现有jdk代理和cglib代理,下面将详细介绍。

jdk代理

jdk代理是基于接口的代理实现,所有的代理类和被代理类继承同一个接口,他们是“兄弟关系”。

jdk代理的使用

首先看看jdk代理是如何使用的,还是拿沸羊羊为例。

/**
 * @version 1.0
 * @author: Pig One
 * @date: 2022/7/28 16:44
 * @decription: 使用jdk代理完成
 *
 */
public class JdkUse {
    interface Send {
        void sendFlower();
        void sendChocolate();
    }


    static class FeiSheepJdk implements Send{
        @Override
        public void sendFlower() {
            System.out.println("送花");
        }

        @Override
        public void sendChocolate() {
            System.out.println("送巧克力");
        }
    }

    public static void main(String[] args) {
        FeiSheepJdk feiSheep = new FeiSheepJdk();
        //jdk代理需要类加载器
        ClassLoader classLoader = JdkUse.class.getClassLoader();
        //第一个参数是类加载器,第二个是要需要增加的类,第三个是回调接口
        Send proxy = (Send)Proxy.newProxyInstance(classLoader,
                new Class[]{Send.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("买花");
                        System.out.println("找到美羊羊");
                        //这个第一个参数是要代理的对象,执行对应的方法
                        Object invoke = method.invoke(feiSheep, args);
                        System.out.println("帮忙美言几句");
                        return invoke;
                    }
                });
        proxy.sendFlower();
        proxy.sendChocolate();
    }
}

并不需要再去创建一个喜羊羊帮忙
回到之前的问题,如果沸羊羊有了新的需求,只需要在接口中添加新的方法并实现,代理类分别调用方法即可。

    interface SendFlower {
        void sendflower();
        void sendChocolate();
    }

但是并不能如意,因为前后增强是写好的。
image.png
可以这么解决:
image.png
image.png
完成了需求。
为什么说这个需求,因为下面会提!!
提一嘴,由于反射的效率很差,所以jdk进行了优化,在使用达到16次之后不再反射调用方法,而是直接用代理类。

手动实现jdk代理

  1. 第一步:静态代理

首先看看简单的实现,和要增强的类继承同一个接口,要使用的时候直接new 一个代理类就行了,但是这样的话功能都是写死的,并不能符合我们动态代理的要求

/**
 * @version 1.0
 * @author: Pig One
 * @date: 2022/7/28 18:04
 * @decription: 代理类
 */
public class $Proxy implements MyJdkUser.Send {
    @Override
    public void sendFlower() {
        System.out.println("买花");
        System.out.println("找到美羊羊");
        new MyJdkUser.MyJdkFeiSheep().sendFlower();
        System.out.println("帮忙美言几句");
    }

    @Override
    public void sendChocolate() {
        System.out.println("买巧克力");
        System.out.println("找到美羊羊");
        new MyJdkUser.MyJdkFeiSheep().sendChocolate();
        System.out.println("帮忙美言几句");
    }
}
  1. 第二步:使用回调接口,回忆一下,我们在使用jdk代理的时候,是不是创建了一个InvocationHandler方法?

好的我们现在就来用这个接口把主动权交付给用户
创建回调接口

    interface InvocationHandler{
        void invoke();
    }

让代理类实现:

public class $Proxy implements MyJdkUser.Send {
    private MyJdkUser.InvocationHandler handler;
    public $Proxy(MyJdkUser.InvocationHandler handler) {
        this.handler=handler;
    }

    @Override
    public void sendFlower() {
        handler.invoke();
    }

    @Override
    public void sendChocolate() {
        handler.invoke();
    }
}

这样用户就能通过回调接口主动实现功能的增强

   $Proxy proxy = new $Proxy(new InvocationHandler() {
            @Override
            public void invoke() {
                System.out.println("买花");
                System.out.println("找到美羊羊");
                new MyJdkFeiSheep().sendChocolate();
                new MyJdkFeiSheep().sendFlower();
                System.out.println("帮忙美言几句");
            }
        });
        proxy.sendChocolate();
        proxy.sendFlower();

这样确实将主动权交付给了用户,但是怎么说呢,交付了,但是没有完全交付!因为回到之前那个_需求,_我不能
根据指定的方法完成指定的增强!

  1. 方法和参数也需要传递!

沸羊羊不可能只送同样的东西,还可能指定数量或者指定东西!这时候就需要传入参数了。
就不得不使用反射了
先改写接口,那么接下来用户可以直接通过方法和参数列表进行方法的反射调用

    interface InvocationHandler{
        void invoke(Method method ,Object[] args);
    }

而在代理类中,通过反射获得方法然后传递给用户。
下面是有有参和无参两种,当然,为了避免重复使用,可以将获取方法的代码抽取出来

    @Override
    public void sendChocolate() {
        Class<?>[] interfaces = $Proxy.class.getInterfaces();
        try {
            Method sendChocolate = interfaces[0].getMethod("sendChocolate");
            handler.invoke(sendChocolate,new Object[0]);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void sendSomething(String msg) {
        Class<?>[] interfaces = $Proxy.class.getInterfaces();
        try {
            Method sendSomething = interfaces[0].getMethod("sendSomething", String.class);
//            System.out.println(parameter.toString());
            handler.invoke(sendSomething,new Object[]{msg});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

抽取出来更加简洁,同时也避免了多次反射获取方法。

public class $Proxy implements MyJdkUser.Send {
    private MyJdkUser.InvocationHandler handler;
    static Method sendFlower ;
    static Method sendChocolate ;
    static Method sendSomething ;

    static {
        Class<?>[] interfaces = $Proxy.class.getInterfaces();
        try {
            sendFlower = interfaces[0].getMethod("sendFlower");
            sendChocolate = interfaces[0].getMethod("sendChocolate");
            sendSomething = interfaces[0].getMethod("sendSomething", String.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public $Proxy(MyJdkUser.InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void sendFlower() {
        //只继承了一个接口,
        handler.invoke(sendFlower, new Object[0]);
    }

    @Override
    public void sendChocolate() {
        handler.invoke(sendChocolate, new Object[0]);
    }

    @Override
    public void sendSomething(String msg) {
//            System.out.println(parameter.toString());
        handler.invoke(sendSomething, new Object[]{msg});
    }
}

那么我们沸羊羊还是只需要命令机器人去做就行了,不过这里用的是反射,而且对于有参的也可以实现。

  $Proxy proxy = new $Proxy(new InvocationHandler() {
            @Override
            public void invoke(Method method, Object[] args) {
                try {
                    String name = method.getName();
                    if (name.equals("sendFlower")) {
                        System.out.println("买花");
                    }else if(name.equals("sendChocolate")){
                        System.out.println("买巧克力");
                    }
                    System.out.println("找到美羊羊");
                    //方法的反射调用
                    method.invoke(myJdkFeiSheep,args);
                    System.out.println("帮忙美言几句");
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        });
        proxy.sendChocolate();
        proxy.sendFlower();
        proxy.sendSomething("hhh");

Snipaste_2022-07-28_21-41-16.png
image.png
于是沸羊羊特别开心,再也不用担心美羊羊喜欢喜羊羊了!
image.png

  1. 代理对象

然而新的问题又出现了!有的时候沸羊羊希望机器人自己做一点,那就也需要把代理对象也传入
在接口中加入即可

    interface InvocationHandler{
        void invoke(Object proxy,Method method ,Object[] args);
    }

在代理的实现中,传入this即表示当前对象。

  1. 返回值

苦人心天不负!有一天美羊羊终于想给沸羊羊一点回应了!
image.png
但是机器人没有返回值的功能,所以需要加入返回值
将之前的接口方法设置为有返回值

    interface InvocationHandler{
        Object invoke(Object proxy,Method method ,Object[] args);
    }

这里可能有一点绕,用户重写了回调接口invoke方法,在方法中获得要增强方法的返回值(反射调用),然后代理类中通过回调接口的返回值返回给用户。
image.png
沸羊羊终于得偿所愿!

cglib代理

cglib代理是用子类实现的,不强求一定要使用接口,同时也可以不是用反射。

cglib代理的使用

我们继续拿沸羊羊开刀,
image.png
cglib代理可以避免使用反射,直接完成方法的调用,在注释里写了三种方法的实现。

/**
 * @version 1.0
 * @author: Pig One
 * @date: 2022/7/28 17:28
 * @decription: cglib代理的使用
 */
public class CglibUse {

    interface Send {
        void sendFlower();
        void sendChocolate();
    }


    static class FeiSheepCglib implements Send {
        @Override
        public void sendFlower() {
            System.out.println("送花");
        }

        @Override
        public void sendChocolate() {
            System.out.println("送巧克力");
        }
    }
    public static void main(String[] args) {
//        FeiSheepCglib feiSheepCglib = new FeiSheepCglib();
        FeiSheepCglib fei = (FeiSheepCglib)Enhancer.create(FeiSheepCglib.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                String name = method.getName();
                if (name.equals("sendFlower")) {
                    System.out.println("买花");
                } else if (name.equals("sendChocolate")) {
                    System.out.println("买巧克力");
                }
                System.out.println("找到美羊羊");
                //需要使用原目标且需要反射
//                method.invoke(feiSheepCglib,args);
                //需要原目标不需要反射
//                methodProxy.invoke(feiSheepCglib,args);
                //不需要原目标,不需要反射
                Object invoke = methodProxy.invokeSuper(proxy, args);
                System.out.println("帮忙美言几句");
                return invoke;
            }
        });
        fei.sendFlower();
        fei.sendChocolate();
    }
}

手动实现cglib代理

  1. 简单实现

cglib是基于子类实现的,简单实现和jdk代理差不多。需要注意的是,方法和类一定不能是final的,否的无法使用!
这里和jdk实现差不多,不赘述了,直接上代码。

/**
 * @version 1.0
 * @author: Pig One
 * @date: 2022/7/28 22:14
 * @decription: 用cglib实现代理
 */
public class MyCglibUser {

    interface MethodInterceptor{
        Object intercept(Object proxy ,Method method,Object[] args,Method methodSuper);
    }

    public static void main(String[] args) {
        FeiSheep feiSheep = new FeiSheep();
        Proxy proxy = new Proxy(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, Method methodSuper) {
                try {
                    System.out.println("找到美羊羊");
                    Object invoke = method.invoke(feiSheep, args);
                    System.out.println("美言几句");
                    return invoke;
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
        proxy.sendFlower();
        proxy.sendFlower(1);
        proxy.sendFlower(10L);

    }
}
//要被代理的类
class FeiSheep {
    public void sendFlower(){
        System.out.println("送花");
    }
    public void sendFlower(int i){
        System.out.println("送"+i+"朵花");
    }
    public String sendFlower(long j){
        System.out.println("送"+j+"朵花");
        return "美羊羊想和沸羊羊约会";
    };
}

/**
 * @version 1.0
 * @author: Pig One
 * @date: 2022/7/28 22:14
 * @decription: cglib的代理类
 */
public class Proxy extends FeiSheep{
    private MyCglibUser.MethodInterceptor methodInterceptor;

    public Proxy(MyCglibUser.MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    //
    static Method sendFlower0;
    static Method sendFloweri;
    static Method sendFlowerj;
    static {
        try {
            sendFlower0 = FeiSheep.class.getMethod("sendFlower");
            sendFloweri = FeiSheep.class.getMethod("sendFlower",int.class);
            sendFlowerj = FeiSheep.class.getMethod("sendFlower",long.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }


    @Override
    public void sendFlower() {
        methodInterceptor.intercept(this,sendFlower0,new Object[0],null);

    }

    @Override
    public void sendFlower(int i) {
        methodInterceptor.intercept(this,sendFloweri,new Object[]{i},null);
    }

    @Override
    public String sendFlower(long j) {
        Object intercept = methodInterceptor.intercept(this, sendFlowerj, new Object[]{j}, null);
        return (String) intercept;
     }
}

image.png

  1. 不是用反射

上面的写法虽然简单易懂,但是还是跟jdk的区别不大,使用反射,而cglib可以不是用反射直接实现。
主要使用下面这个静态方法,第三个参数的见:https://www.lmlphp.com/user/16709/article/item/526082/
使用MethodProxy方法

 //里面的参数是,第一个是指要增强的类,第二个是代理类,第三个是参数和返回值,第四个是要增强的方法,第五个是带原始功能的方法(即改进之后的方法)
            sendFlower0Proxy =MethodProxy.create(FeiSheep.class,Proxy.class,"()V","sendFlower","sendFlowerSuper");

代理类:

/**
 * @version 1.0
 * @author: Pig One
 * @date: 2022/7/28 22:14
 * @decription: cglib的代理类
 */
public class Proxy extends FeiSheep{
    private MyCglibUser.MethodInterceptor methodInterceptor;
    public Proxy(MyCglibUser.MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    //
    static Method sendFlower0;
    static Method sendFlowerI;
    static Method sendFlowerJ;
    static MethodProxy sendFlower0Proxy;
    static MethodProxy sendFlowerIProxy;
    static MethodProxy sendFlowerJProxy;
    static {
        try {
            sendFlower0 = FeiSheep.class.getMethod("sendFlower");
            sendFlowerI = FeiSheep.class.getMethod("sendFlower",int.class);
            sendFlowerJ = FeiSheep.class.getMethod("sendFlower",long.class);
            //里面的参数是,第一个是指要增强的类,第二个是代理类,第三个是参数和返回值,第四个是要增强的方法,第五个是带原始功能的方法(即改进之后的方法)
            sendFlower0Proxy =MethodProxy.create(FeiSheep.class,Proxy.class,"()V","sendFlower","sendFlowerSuper");
            sendFlowerIProxy =MethodProxy.create(FeiSheep.class,Proxy.class,"(I)V","sendFlower","sendFlowerSuper");
            sendFlowerJProxy =MethodProxy.create(FeiSheep.class,Proxy.class,"(J)Ljava/lang/String;","sendFlower","sendFlowerSuper");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
    public void sendFlowerSuper(){ super.sendFlower();}
    public void sendFlowerSuper(int i){ super.sendFlower(i);}
    public String sendFlowerSuper(long j){ return super.sendFlower(j);}

    @Override
    public void sendFlower() {
        try {
            methodInterceptor.intercept(this,sendFlower0,new Object[0],sendFlower0Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void sendFlower(int i) {
        try {
            methodInterceptor.intercept(this,sendFlowerI,new Object[]{i},sendFlowerIProxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public String sendFlower(long j) {
        Object intercept = null;
        try {
            intercept = methodInterceptor.intercept(this, sendFlowerJ, new Object[]{j},sendFlowerJProxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
        return (String) intercept;
     }
}

 public static void main(String[] args) {
        FeiSheep feiSheep = new FeiSheep();
        Proxy proxy = new Proxy(new MethodInterceptor() {
            @Override
            public Object intercept(Object p, Method method, Object[] args, MethodProxy methodSuper) throws Throwable {

                    System.out.println("找到美羊羊");
                    //反射
//                    Object invoke = method.invoke(feiSheep, args);
                   //需要目标的反射
//                    Object invoke = methodSuper.invoke(feiSheep, args);
                    //不需要目标的反射
                    Object invoke = methodSuper.invokeSuper(p, args);
                    System.out.println("美言几句");
                    return invoke;

            }
        });
        proxy.sendFlower();
        proxy.sendFlower(1);
        proxy.sendFlower(10L);

    }

image.png

  1. MethodProxy

MethodProxy底层是用一个fastclass实现的,fastclass是一个代理类,根据方法生成对应的签名,然后方法过来的时候进行判断是哪个方法,选择以后直接使用。前面出了点小问题找了很久,有机会再补这里吧。

优点及应用场景

高扩展性
Spring的AOP就是使用的代理模式。

参考

黑马

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值