设计模式——代理模式详解(Java版)_java代理模式详解-程序员宅基地

技术标签: java  代理模式  设计模式  

一,什么是代理模式?

给一个对象提供一种代理对象以控制对该对象的访问。
简单点理解:
目标对象:原对象,我们需要通过代理对象控制它的访问,扩展其功能。
代理对象:代理模式产生的对象,是原对象的替身,在原有基础上进行修改。
在不改变原对象代码的基础上对原对象的功能进行扩展
再简单点理解:
比如点外卖事件
你想吃麻辣烫,自己又不想去店里吃,所以你点外卖,此时的外卖小哥,可以看作为代理对象。而你又想在吃完麻辣烫后喝一杯珍珠奶茶,所以你又联系外卖小哥帮你去店里买一杯。这个过程可以理解为加的扩展功能。

二,为什么要使用代理模式

降低了系统的耦合度,扩展性好
可以起到保护目标对象的作用

三,代理模式的三种创建方式

例子:
顾客想要点一份鱼香肉丝,让外卖员送过来,并且想要帮忙带一份珍珠奶茶。
分析:
顾客:目标对象
外卖员:代理对象
点珍珠奶茶:扩展功能

1.静态代理

需要我们手写代理类。两种形式:

1.接口方式实现:让目标对象和代理对象都实现一个共同接口。那么这两个类就有了公共的方法,就可以在代理对象中实现对目标对象功能的扩展。
实现代码如下

OrderInterface接口:

public interface OrderInterface {
    public String order(String foodName);
}

customer类:

public class Customer implements OrderInterface{
    public String order(String foodName){
        return "已下单点了"+foodName;
    }

}

DeliveryClerk类:

public class DeliveryClerk implements OrderInterface{
    //把原来的对象传入并保存到成员位置。也就是目标类对象
    private OrderInterface source;
    public DeliveryClerk(OrderInterface source) {
        this.source=source;
    }

    @Override
    public String order(String foodName) {
        String result = source.order(foodName);
        System.out.println("已经接订单在送去的路上");
        return result+"买了一杯珍珠奶茶";
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
       //1.创建顾客对象
        Customer customer = new Customer();
       //2.创建代理对象
        DeliveryClerk deliveryClerk = new DeliveryClerk(customer);
       //3.调用方法
        String result = deliveryClerk.order("鱼香肉丝盖饭");
        System.out.println(result);
    }
}

2.继承方式:让代理对象继承目标对象,那么代理对象就拥有目标对象的方法,同时又可以对其功能进行扩展。
实现代码如下

customer类:

public class Customer {
    public String order(String foodName){
        return "已下单点了"+foodName;
    }
}

DeliveryClerk类:

public class DeliveryClerk extends Customer{
    @Override
    public String order(String foodName) {
        String result = super.order(foodName);
        System.out.println("已经接订单在送去的路上");
        return result+"买了一杯珍珠奶茶";
    }
}

静态代理维护依然复杂,一旦接口或父类发生改变,所有相关的类或接口就都得进行维护。

2.动态代理

是在内存中生成代理对象的一种技术
无需手写代理类,也不会存在代码编译的过程。运用在内存中生产代理类的技术在JVM的运行区造一个代理对象,只需对需要修改的部分进行编辑。

1.基于jdk接口的动态代理

实际上就是在内存中生产一个对象,该对象实现了指定的目标对象的所有接口,代理对象和目标对象是兄弟关系。
jdk自带动态代理技术,需要使用一个静态方法来创建代理对象,它需要目标对象必须实现接口,生产的代理对象和原对象都实现相同的接口。

编写流程

  1. 准备一个目标类对象,也就是顾客对象
  2. 使用jdk的API动态的生成代理对象
  3. 调用代理对象执行相应的方法

参数解释:

1.ClassLoader loader:

固定写法,指定目标类对象的类的加载器即可,用于加载目标类及其接口的字节码文件,通常使用目标类的字节码文件调用getClassLoader()方法可得到。

2.Class<?>[] interfaces:

固定写法,指定目标类的所以接口的字节码对象的数组,通常使用目标类的字节码文件调用getinterfaces()方法可得到。

3.InvocationHander h:

这个参数是一个接口,主要关注它里面的一个方法,它会在代理类调用方法时执行,也就是说,在代理类对象中调用的任何方法都会执行invoke()方法。所以在此方法中进行代码的扩展。

invoke()方法中参数的含义:

1.proxy:就是代理类对象的一个引用也就是Proxy.newProxyInstance()方法的返回值;此引用几乎不会用到。
2.method:对应的就是触发invoke执行的方法的Method对象。假如我们调用了Xxx方法,该方法触发了invoke执行,那么method就是Xxx方法对应的反射对象Method。
3.args:代理对象调用方法时传入的实际参数

OrderInterface接口:

public interface OrderInterface {
    public String order(String foodName);
    public void test1();
    public void test2();
}

Customer类:

public class Customer implements OrderInterface {
    public String order(String foodName){
        return "已下单点了"+foodName;
    }
    @Override
    public void test1() {
    }
    @Override
    public void test2() {

    }

}

Test 类:

public class Test {
    public static void main(String[] args) {
        //1. 准备一个目标类对象,也就是顾客对象
        Customer customer = new Customer();
        //2. 使用jdk的API动态的生成代理对象
        OrderInterface deliveryClerk = (OrderInterface) Proxy.newProxyInstance(customer.getClass().getClassLoader(),
                customer.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if ("order".equals(method.getName())) {
                            Object result = method.invoke(customer, args);
                            System.out.println("已经接订单在送去的路上");
                            return result + "买了一杯珍珠奶茶";
                        } else {
                            return method.invoke(customer, args);
                            //使用method反射调用,在原对象中执行方法,并不改变逻辑,也就是说原封不动调用原来的逻辑
                        }
                    }
                }
                );
        //3. 调用代理对象执行相应的方法
        String result=deliveryClerk.order("鱼香肉丝盖饭");
        System.out.println(result);
    }
}

2.基于cglib父类的动态代理

第三方cglib动态代理技术也是可以使用一个静态代理方法来创建代理对象,它不要求目标类实现接口,但是要求目标类不能是最终类,也就是说不能被final修饰。因为cglib是基于目标类生成该类的一个子类作为代理类,所以目标对象必须可以被继承。

基于父类的动态代理是在内存中生成一个对象,该对象继承了原对象,所以代理对象实际上就是原对象的子类。

编写流程

首先需要导包

  1. 准备一个目标类对象,也就是顾客对象
  2. 使用cjlib创建代理对象
  3. 调用代理对象执行相应的方法

参数解释:

1.Class type:

指定我们要代理的目标类的字节码对象,也就是指定目标类的类型。
1.callback:

也是一个接口,只是名称定义的作用。只是让别的接口去继承它,提供一个方法它会在合适的时候回来调用它。通常使用其子类
它的子类MethodInterceptor(方法拦截器)接口,其只有一个方法,叫做intercept()方法

intercept()方法中的参数:

1.proxy:就是代理类对象的一个引用也就是Enharcer.create()方法的返回值;此引用几乎不会用到。
2.method:对应的就是触发intercept执行的方法的Method对象。假如我们调用了Xxx方法,该方法触发了invoke执行,那么method就是Xxx方法对应的反射对象Method。
3.args:代理对象调用方法时传入的实际参数
4.methodProxy:方法的代理对象,一般不做处理,暂时忽略。

Customer类

public class Customer {
    public String order(String foodName){
        return "已下单点了"+foodName;
    }
    public void test1() {
    }
    public void test2() {
    }
}

Test类:

public class Test {
    public static void main(String[] args) {
        // 创建一个目标类对象,也就是顾客对象
        Customer customer = new Customer();
        // 使用CGLIB创建代理对象
        Customer deliveryClerk = (Customer) Enhancer.create(customer.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // 判断,如果是order方法,则增强
              if ("order".equals(method.getName())) {
                            Object result = method.invoke(customer, args);
                            System.out.println("已经接订单在送去的路上");
                            return result + "买了一杯珍珠奶茶";
                        } else {
                            return method.invoke(customer, args);
                            //使用method反射调用,在原对象中执行方法,并不改变逻辑,也就是说原封不动调用原来的逻辑
                        }
                    }
                }
                );
        //3. 调用代理对象执行相应的方法
        String result=deliveryClerk.order("鱼香肉丝盖饭");
        System.out.println(result);
    }
}

3.兄弟关系 VS 父子关系

兄弟关系

并列关系,不能互相转换,包容性比较差,在spring框架中,如果配置jdk的动态代理方式,一定要用接口类型接收代理类。

父子关系

代理对象是可以用父类的引用接收的。

四,总结

1.代理模式在Java开发中是广泛应用的,特别是在框架中,底层原理经常设计到。
2.静态代理需要手写代码且维护,修改非常繁琐,会引入很多工作量。所以常用动态代理。
动态代理中
3.在spring框架中默认支持了两种动态代理。如果指定的目标类实现了接口,spring中就会自动用jdk动态代理,如果没有实现接口就会自动用cglib方式。
4.我们在开发时,由于基于jdk的动态代理要求比较多,更不容易实现。所以很多人配置使用cglib动态代理。
5.如果使用dubbo+zookeeper,底层进行代理时最好配置使用cglib的方式进行代理。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_54796785/article/details/125335470

智能推荐

STM32最小系统_stm32最小系统示例-程序员宅基地

文章浏览阅读2.7w次,点赞5次,收藏21次。STM32简介STM32是一款高性能,低功耗,低成本的嵌入式ARM芯片,其家族产品大致划分如图STM32型号说明,以STM32F103ZET6芯片为例:STM32F103ZET6ARM Cortex-M内核32位微控制器芯片系列增强型席系列引脚数,Fash容量封装类型工作温度范围引脚数取值说明取值引脚数T引脚数位36R引脚数位64V引脚数位144I引脚数位176Flash容量取值说明取值_stm32最小系统示例

vue element-ui悬浮显示_elementui 悬浮窗-程序员宅基地

文章浏览阅读2k次。【代码】vue element-ui悬浮显示。_elementui 悬浮窗

Spring AOP异常处理(error at ::0 formal unbound in pointcut)-程序员宅基地

文章浏览阅读2.4k次。【问题背景】基于XML配置开发AspectJ和基于注解开发AspectJ时报错:error at ::0 formal unbound in pointcut【问题分析】切点绑定失败,必然是AspectJ相关配置出现问题。【问题解决】逐一查看配置,包括每个切点的定义,切面配置,通知配置。经排查,发现是“异常通知”配置有误,没有将异常throwing出去。修改前: <aop:after-throwing method="exceptAdvice" pointcut-re_error at ::0 formal unbound in pointcut

Geohash算法-程序员宅基地

文章浏览阅读2.3w次,点赞28次,收藏117次。用户附近位置计算经纬度与物理距离介绍经纬度是经度与纬度的合称组成一个坐标系统,称为地理坐标系统,它是一种利用三度空间的球面来定义地球上的空间的球面坐标系统,能够标示地球上的任何一个位置。在一定误差范围内,通常情况下,经纬线和米的换算为:经度或者纬度0.00001度,约等于1米。以下表格列出更细致的换算关系:在纬度相等的情况下在经度相等的情况下经度每隔0.00001度,距离相差约1米;每隔0.0001度,距离相差约10米;每隔0.001度,距离相差约100米;每隔0.01度,距离_geohash

【STM32F429的DSP教程】第30章 STM32F429复数浮点FFT(支持单精度和双精度)_1024 fft 旋转因子-程序员宅基地

文章浏览阅读799次。完整版教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547第30章 STM32F429复数浮点FFT(支持单精度和双精度)本章主要讲解复数浮点FTT,支持单精度和双精度。目录30.1 初学者重要提示30.2 复数浮点FFT说明30.2.1 功能描述30.2.2 浮点FFT30.3 单精度函数arm_cfft_f32的使用(含幅频和相频)30.3.1 函数说明30.3.2 使用举..._1024 fft 旋转因子

信息学奥赛一本通 2030:【例4.16】找素数_信息学奥赛一本通2030:【例4.16】找素数-程序员宅基地

文章浏览阅读6.1k次,点赞4次,收藏2次。【题目链接】ybt 2030:【例4.16】找素数【题目考点】1. 枚举2. 质数质数的因数只有1和它本身。判断数a是否是质数:循环变量i从2开始循环到a\sqrt{a}a​,若存在a能整除的数字(即a的因数),那么a不是质数。否则a是质数。bool isPrime(int a)//判断a是否是质数,已知a >= 2{ for(int i = 2; i <= sqrt(a); i++) { if(a % i == 0) _信息学奥赛一本通2030:【例4.16】找素数

随便推点

最小生成树MST详解_mst最小生成树-程序员宅基地

文章浏览阅读3.2k次,点赞9次,收藏42次。最小生成树(MST)是图的一个子集。本文将介绍两种常用的算法:Kruskal和Prim来寻找最小生成树。_mst最小生成树

听GPT 讲Rust源代码--src/tools(18)-程序员宅基地

文章浏览阅读1.7k次,点赞20次,收藏21次。它们用于定位代码中的特定位置,支持宽字符的处理,并提供行索引和字符编码相关的功能。综上所述,rust/src/tools/tier-check/src/main.rs这个文件的主要作用是实现"tier-check"工具,用于检查Rust编译器的编译层级,并提供有关Rust构建系统编译层级的信息。文件rust/src/tools/rust-analyzer/lib/line-index/src/lib.rs是Rust语言的语法分析器rust-analyzer的一个核心组件,用于处理代码的行列信息。

配置NetBackup 7 for oracle 10g rac-程序员宅基地

文章浏览阅读467次。一、环境概述:一台装有fedora13的pc,使用vmware workstation搭建了一个solaris cluster环境,使用的os是solaris10u9。装有oracle 10g RAC。两节点的名称及IP对应如下:其中fedora13是装有netbackup 7.0.1服务端以及media server端,node04和node05是客户端。目前各个实例已经处于onli..._oracle rac netbackup

linux安装oracle12c_linuxoracle-清理软件重装12c-程序员宅基地

文章浏览阅读2k次,点赞2次,收藏13次。上一篇做了安装oracle一些系统配置安装oracle12c单实例数据一、安装oracle的配置1.修改内核参数①编辑内核参数文件vim /etc/sysctl.conf②将内核配置粘贴到该文件中fs.file-max = 6815744kernel.sem = 250 32000 100 128kernel.shmmni = 4096kernel.shmall = 1073741824kernel.shmmax = 4398046511104kernel.panic_on_oops_linuxoracle-清理软件重装12c

详解Unity中的导入3D模型_unity导入3d模型-程序员宅基地

文章浏览阅读2.8w次,点赞26次,收藏168次。三维模型可以表示任何现实世界中存在的物体,自然也可以表示任何游戏世界中存在的物体,比如地面,山川河流,花草树木,建筑,人物,都需要使用三维模型来表示。一般我们使用一些建模软件来建出游戏中需要的模型,然后导入到Unity中使用,本篇就来介绍介绍,一整个的3D模型制作导入流程以及需要注意的问题。_unity导入3d模型

面向对象——接口(interface)_interface的定义和作用-程序员宅基地

文章浏览阅读713次。接口_interface的定义和作用

推荐文章

热门文章

相关标签