代理模式(Proxy Pattern)

一、定义

代理模式(Proxy Pattern):结构型模式之一,为真实对象提供一个代理,从而控制对真实对象的访问

二、UML类图

在这里插入图片描述

三、角色职责

  • 抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。
  • 代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
  • 真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

四、代码实现

在这里插入图片描述
前言:举个栗子,我马上就要过生日了,为了奖励辛苦了一年的自己,我决定给自己买一台iPhone13,于是我打开了苹果官网,发现256G的iPhone13居然需要6799元,这实在是太贵了!我咨询香港的朋友,发现港版的iPhone13只需要6299元,整整便宜了500块,于是我决定去香港购买一台iPhone13,这个时候又想起来,现在香港的疫情非常严重,我可能去的时候好好的,回不来了,于是我想到了可以让代购帮我购买一台iPhone13。这个时候我们就用到了代理模式。

  • 静态代理:要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。

    售卖接口(抽象角色 Subject)

    public interface iPhone13 {
        void sell();
    }
    

    香港AppleStore(真实角色 RealSubject)

    public class HongKongAppleStore implements iPhone13 {
        @Override
        public void sell() {
            System.out.println("出售iPhone13");
        }
    }
    

    代购(代理角色 Proxy)

    public class DelegatedPurchase implements iPhone13 {
    
        private iPhone13 iphone13;
    
        DelegatedPurchase(iPhone13 iphone13) {
            this.iphone13 = iphone13;
        }
    
        @Override
        public void sell() {
            System.out.println("开始代购iPhone13");
            iphone13.sell();
            System.out.println("代购成功");
        }
    }
    

    测试类

    public class ProxyTest {
        public static void main(String[] args) {
            // 创建AppleStore
            iPhone13 iPhone = new HongKongAppleStore();
            // 帮助用户代购iPhone13
            iPhone13 delegatedPurchase = new DelegatedPurchase(iPhone);
            delegatedPurchase.sell();
        }
    }
    

    运行结果

    开始代购iPhone13
    出售iPhone13
    代购成功
    
  • 动态代理:在程序运行时创建的代理方式被成为动态代理。代理类并不是在Java代码中定义的,而是在运行时根据代码中的“指示”动态生成。

    我们只需要修改代理类和测试类即可。
    代购(代理角色 Proxy)

    public class DelegatedPurchase implements InvocationHandler {
    
        private iPhone13 iphone13;
    
        DelegatedPurchase(iPhone13 iphone13) {
            this.iphone13 = iphone13;
        }
    
        /**
         * 代理方法
         *
         * @param proxy  代理对象
         * @param method 调用的方法,被封装成一个方法对象
         * @param args   调用方法时的实际参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("开始代购iPhone13");
            // 通过反射去调用被代理类的方法
            Object invoke = method.invoke(iphone13, args);
            System.out.println("代购成功");
            return invoke;
        }
    }
    

    测试类

    public class ProxyTest {
        public static void main(String[] args) {
            // 创建AppleStore
            iPhone13 iPhone = new HongKongAppleStore();
            // 帮助用户代购iPhone13
            DelegatedPurchase proxy = new DelegatedPurchase(iPhone);
            /*
             * 通过Proxy.newProxyInstance()方法在运行时动态创建代理类
             * ClassLoader loader,  类加载器,这里主要是选择代理的类加载器
        	 * lass<?>[] interfaces, 被代理类的所有接口
         	 * InvocationHandler h  代理类
         	 */
            iPhone13 delegatedPurchase = (iPhone13) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), iPhone.getClass().getInterfaces(), proxy);
            delegatedPurchase.sell();
        }
    }
    
  • CGLib实现动态代理

    我们只需要修改代理类与测试类即可。
    Maven依赖

    <dependency>
       <groupId>cglib</groupId>
       <artifactId>cglib</artifactId>
       <version>2.2.2</version>
    </dependency>```
    

    代购(代理角色 Proxy)

    public class DelegatedPurchase implements MethodInterceptor {
        private Enhancer enhancer = new Enhancer();
    
        public Object getProxy(Class clazz) {
            // 设置需要创建子类的类
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            // 通过字节码技术动态创建子类实例
            return enhancer.create();
        }
    
        // 实现MethodInterceptor接口方法
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("开始代购iPhone13");
            // 通过代理类调用父类中的方法
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("代购成功");
            return result;
        }
    }
    

    代理对象的生成由Enhancer类实现,Enhancer是CGLib的字节码增强器,可以很方便的对类进行扩展。
    Enhancer 创建代理对象的大概步骤如下:
    1、生成代理类 Class 二进制字节码。
    2、通过 Class.forname() 加载字节码文件, 生成 Class 对象。
    3、过反射机制获得实例构造, 并创建代理类对象。
    CGLib定义的intercept() 接口方法,拦截所有目标类方法的调用。其中obj代表目标类的实例,method为目标类方法的反射,args为方法的动态入参, proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法

    测试类

    public class ProxyTest {
        public static void main(String[] args) {
            DelegatedPurchase proxy = new DelegatedPurchase();
            iPhone13 delegatedPurchase = (iPhone13) proxy.getProxy(HongKongAppleStore.class);
            delegatedPurchase.sell();
        }
    }
    

    使用JDK创建动态代理有一个限制,即它只能为接口创建代理实例,对于没有定义接口的业务方法的类,可以使用CGLib进行动态代理,CGLib是一个强大的, 高性能的代码生成库,被广泛应用于AOP框架,用以提供方法拦截操作。CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用,并织入横切逻辑。根据CGLib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么CGLib代理是无法正常工作的,因为final类型方法不能被重写。

五、源码分析

JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类,这两个类相互配合,入口是Proxy。
Proxy有个静态方法:getProxyClass(ClassLoader, interfaces),只要你给它传入类加载器和一组接口,它就给你返回代理Class对象。用通俗的话说,getProxyClass()这个方法,会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象的。
所以,一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理Class,通过代理Class即可创建代理对象。

静态代理
静态代理
动态代理
动态代理
不过实际编程中,一般不用getProxyClass(),而是使用Proxy类的另一个静态方法:Proxy.newProxyInstance(),直接返回代理实例,连中间得到代理Class对象的过程都帮你隐藏。那么我们就来讲一讲Proxy.newProxyInstance()。
在这里插入图片描述
newProxyInstance()方法有三个参数:
ClassLoader loader:被代理类的装载器
Class<?>[] interfaces:被代理类的所有接口(生成哪个对象的代理对象,通过接口指定)
InvocationHandler h:代理类在执行对应方法的时候要调用的方法

那newProxyInstance()是如果通过接口构造一个代理对象的呢?我们进入源码一探究竟。
在这里插入图片描述
方法中会通过getProxyClass0(loader, intfs)生成代理类,然后获取代理类的构造方法,再根据代理类的构造方法实例化代理类对象。那getProxyClass0()是如果生成代理类的呢?我们点进这个方法。
在这里插入图片描述
我们点进代理类缓存类。
ttps://img-blog.csdnimg.cn/aefc8c5c1e154e668e0a45ea81426dac.png)
原来接口工厂和代理类工厂都通过kv的形式存储在代理类缓存中了,然后会从这个缓存中get()代理类。那他是如果get()到代理类的呢?我们点进get()方法。在这里插入图片描述
其中key和parameter就代表我们传进去的两个参数,那如何根据接口生成subKey呢?我们再点进apply()方法。
在这里插入图片描述
这里apply()方法会根据你接口的数量去进行实例化生成subKey对象。然后通过subKey去Map中拿到我们所需要的代理类。
在这里插入图片描述
如果Map中没有我们的代理类,就会调用Factory()方法。然后factory就有值了,但这个时候supplier肯定也是空,所以会把factory的值赋值给supplier。
在这里插入图片描述
之后循环第二次while (true) 的时候,supplier就不为null了,再去调用get()方法。
在这里插入图片描述
这里就会将参数生成key,再存入Map中。这样就可以get()到代理类了。那我们的代理类究竟长什么样子呢?我们先找到ProxyGenerator类。
在这里插入图片描述
然后在里面找到saveGeneratedFiles复制里面的值。我这里是JDK1.8版本,其他版本的可以对应着去找一下。在这里插入图片描述在我们的测试类中加入下面这行代码运行即可。System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
在这里插入图片描述
这样我们就成功的生成了代理类。
在这里插入图片描述
在这里插入图片描述
所有通过动态代理实现的方法全部通过invoke() 调用。
在这里插入图片描述
invoke()方法的三个参数:
Object proxy:代理对象
Method method:反射调用的方法,被封装成一个方法对象
Object[] args:调用方法时的实际参数、

所以看到这里,大家应该就非常明了了。那我现在举个栗子,一个大内太监(接口Class),空有一身武艺(类信息),但是无法传给后人。现在江湖上有个妙手神医(Proxy类),发明了克隆大法(newProxyInstance),不仅能克隆太监的一身武艺,还保留了小DD(构造器)…(这到底是道德の沦丧,还是人性的扭曲,欢迎走进动态代理)

六、优缺点分析

静态代理
优点:静态代理对客户隐藏了被代理类接口的具体实现类,在一定程度上实现了解耦合,同时提高了安全性。
缺点:静态代理只能对某个固定接口的实现类进行代理服务,其灵活性不强。故一般大项目不会选择静态代理。

动态代理

  • JDK动态代理

    1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。
    2、JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口。

  • CGLib动态代理

    1、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final
    2、CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理。

七、适用场景

想要对类中的方法批量处理,或者想要再目标对象的基础上 , 对目标对象的功能进行增强,也就是扩展对象的功能时我们可以使用代理模式。假如一个类中的方法有100个,我们想要对这些方法都做日志处理。这个时候我们就可以使用代理,简化代码,不需要在每个方法调用的时候都做日志处理。

八、总结

代理模式的设计动机是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。在代理模式中,代理对象主要起到一个中介的作用,用于协调与连接调用者(即客户端)和被调用者(即目标对象),在一定程度上降低了系统的耦合度,同时也保护了目标对象。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值