gof23设计模式之代理模型

news2024/11/6 3:06:50

1. 代理模式

1.1. 概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理类在编译期就生成,而动态代理代理类则是在Java运行时态生成。动态代理有分有JDK代理和CGlib代理两种。

1.2.结构

代理(proxy)模式分为三种角色:

  • 抽象主题(Subject)类:通过接口或抽象声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真是主题的功能。

2.静态代理(火车站卖票)

如果要买火车票的话,需要去火车站买票,坐车到火车站、排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票方便多了。火车站是目标对象,代售点是代理对象。
在这里插入图片描述

/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:42
 *      卖火车票的接口
 */
public interface SellTickets {

    void sell();
}


/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:
 *      火车站类
 */
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}


/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:44
 *      代售点类
 */
public class ProxyPoint implements SellTickets {

    // 生命火车站类对象
    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        System.out.println("代售点收取一些服务费用");
        trainStation.sell();
    }
}
/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:46
 */
public class Client {
    public static void main(String[] args) {
        // 创建代售点对象
        ProxyPoint proxyPoint = new ProxyPoint();
        // 调用方法进行买票
        proxyPoint.sell();
    }
}

在这里插入图片描述

3.JDK动态代理

Java提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:42
 *      卖火车票的接口
 */
public interface SellTickets {

    void sell();
}




/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:
 *      火车站类
 */
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}



import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:49
 *      获取代理对象的工厂类
 *          代理类也实现了对应的接口
 */
public class ProxyFactory {

    // 声明目标对象
    private TrainStation station = new TrainStation();

    // 获取代理对象的方法
    public SellTickets getProxyObject() {
        // 返回代理对象即可
        /*
           ClassLoader loader  类加载器,用于加载代理类,可以通过目标对象获取类加载器
           Class<?>[] interfaces   代理类实现的接口的字节码对象
           InvocationHandler h  代理对象的调用处理程序
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        Object proxy  代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不用
                        Method method  对接口中的方法进行封装的method对象
                        Object[] args   调用方法的实际参数

                        返回值:   方法的返回值
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //System.out.println("invoke方法执行了");
                        System.out.println("代售点收取一定的服务费用(JDK动态代理)");

                        // 执行目标对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                });
        return proxyObject;
    }

}



/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:55
 */
public class Client {
    public static void main(String[] args) {
        // 获取代理对象
        // 1.常见代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory();
        // 2.使用factory对象的方法获取代理对象
        SellTickets proxyObject = proxyFactory.getProxyObject();
        // 调用卖的方法
        proxyObject.sell();

        System.out.println(proxyObject.getClass());

        // 让程序一直运行
        while (true) {

        }
    }
}


ProxyFactory不是代理模式中所说的代理类,而代理类程序在运行过程中动态的在内存中生成的类。

获得字节码的方法
在VM option中添加 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在这里插入图片描述
在根目录生成
在这里插入图片描述

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.xfcy.proxy.jdk_proxy.SellTickets;
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 SellTickets {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    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 void sell() throws  {
        try {
            super.h.invoke(this, m3, (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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.xfcy.proxy.jdk_proxy.SellTickets").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

  • 从这个代理类($Proxy)实现了SellTicks。这也就印证了我们之前说的是真实类和代理类实现同样的接口。
  • 代理类($Proxy0)将提供了匿名的内部类对象传递给了父类

动态代理的执行流程是什么样?

  • 在测试类中通过代理对象调用sell()方法
  • 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
  • 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
  • invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

4.CGLIB动态代理

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用
了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提
供了很好的补充。

引入jar包坐标:

<!--        CGLIB动态代理 -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.12</version>
        </dependency>

/**
 * @author 晓风残月Lx
 * @date 2023/6/30 19:
 *      火车站类
 */
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author 晓风残月Lx
 * @date 2023/6/30 20:50
 *      代理对象工厂  用来获取代理对象
 */
public class ProxyFactory implements MethodInterceptor {

    // 声明火车站对象
    private TrainStation station = new TrainStation();

    public TrainStation getProxyObject() {
        // 创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperclass(TrainStation.class);
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        TrainStation proxyObject = (TrainStation)enhancer.create();
        return proxyObject;
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代售点收取一定的服务费用(CGLIB代理)");
        // 调用目标对象的方法
        Object obj = method.invoke(station, objects);
        return obj;
    }
}
/**
 * @author 晓风残月Lx
 * @date 2023/6/30 20:55
 */
public class Client {
    public static void main(String[] args) {
        // 创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        // 获取代理对象
        TrainStation proxyObject = factory.getProxyObject();
        // 调用代理对象中的sell方法卖票
        proxyObject.sell();

    }
}

5.三种代理的对比

  • jdk代理和CGLIB代理
    使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在
    JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者
    方法进行代理,因为CGLib原理是动态生成被代理类的子类。
    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代
    理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率
    低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代
    理,如果没有接口使用CGLIB代理。
  • 动态代理和静态代理
    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中
    的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们
    可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实
    现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

6.优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度

缺点:

  • 增加了系统的复杂度

7.使用场景

  • 远程(Remote) 代理
    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理器中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务的一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
  • 防火墙(Firewall)代理
    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再给它转给你的浏览器
  • 保护(Protect or Access) 代理
    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/710864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kubernetes对象深入学习之一:概览

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 关于《Kubernetes对象深入学习》系列 在client-go的学习和使用过程中&#xff0c;不可避免涉及到对象相关的代码&#xff0c;经常面临一个尴尬的现象&#x…

PCL点云处理之多角度剖面切割(一百九十五)

PCL点云处理之多角度切割点云剖面(一百九十五) 一、算法介绍二、具体实现1.沿法向量方向切割剖面2.沿竖直方向切割剖面3.沿水平方向切割剖面一、算法介绍 点云的剖面往往隐藏着很多有用信息,而且分析更加简单一些,这里自己实现一系列不同角度的点云剖面切割,包括沿着法向量…

车载软件架构 —— 闲聊几句AUTOSAR OS(七)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生在世,最怕的就是把别人的眼光当成自己生活的唯一标…

Framework - AMS

一、概念 Android10&#xff08;API29&#xff09;开始&#xff0c;ActivityTaskManagerService 接管 ActivityManagerService。 二、启动ATMS过程 三、启动APP & 跳转Activity过程 3.1 Activity → ATMS 启动一个 APP 或跳转一个 Activity 都是调用的 startActivity()&a…

数据结构--串的存储结构

数据结构–串的存储结构 串的顺序存储 静态数组实现(定长顺序存储) #define MAXLEN 255 typedef struct {char ch[MAXLEN];int length; }SString;动态数组实现(堆分配存储) typedef struct {char* ch;int length; }HString;int main() {HString S;S.ch (char*)malloc(sizeo…

问题解决:centos7异常关闭后无法开机

前言&#xff1a;主机卡死&#xff0c;直接关了电脑电源&#xff0c;虚拟机中的centos7 产生错误&#xff0c;无法开机 重点是取消挂载。很多文章都提到了xfs_repair /dev/dm-0 , 但是不适用我遇到的情况。 # ls /dev/mapper umount /dev/mapper/centos-root xfs_repair -v -…

[洛谷]B3601 [图论与代数结构 201] 最短路问题_1(负权)(spfa)

SPFA模板啦~ 直接上ACcode: #include<bits/stdc.h> using namespace std; //#define int long long #define inf 2147483647 const int N15e310,M2*N; int dis[N],head[N],cnt; bool vis[N]; int n,m; struct E {int to,w,next; } e[M]; queue<int>q; void add(in…

U-Boot移植 - 1_嵌入式Linux系统移植概述

文章目录 1. 嵌入式Linux系统移植概述2. 实验开发板简介3. U-Boot简介4. NXP uboot测试 1. 嵌入式Linux系统移植概述 Linux 的移植主要包括3部分&#xff1a; 移植「bootloader 代码」&#xff0c; Linux 系统要启动就必须需要一个 bootloader 程序&#xff0c;也就说芯片上电…

【Android Framework (十) 】- ContentProvider

文章目录 知识回顾启动第一个流程initZygote的流程system_serverServiceManagerBinderLauncher的启动AMSservicebinderService 前言源码分析1.使用方法2.ContentProvider实现类。3.使用方法4.注册Observer正文 拓展知识 总结 知识回顾 启动第一个流程init 1&#xff0c;挂载文…

基于eBPF技术的云原生可观测实践

** 基于eBPF技术的云原生可观测实践 ** eBPF技术是Linux内核3.15版本中引入的全新设计&#xff0c;自从2014年发布以来&#xff0c;一直都备受瞩目。在过去几年中&#xff0c;基于eBPF技术的实践和工程落地层出不穷&#xff0c;出现了爆发式的增长。2015年微软、Google、Face…

浏览器里的任意一个请求通过postman生成对应的代码

大多数情况下&#xff0c;我们都是不知道某个网站的get或者post请求以及其他请求&#xff08;比如说PUT请求等&#xff09;是该加哪些headers和cookie才能用代码请求成功&#xff0c;这时就需要下面的操作了。 浏览器里的任意一个请求通过postman生成对应的代码&#xff1a; …

外观模式的学习与使用

1、外观模式的学习 当你在开发软件系统时&#xff0c;系统内部的子系统可能会变得非常复杂&#xff0c;包含了许多相互关联的类和接口。在使用这些子系统时&#xff0c;你可能需要调用多个类和方法才能完成所需的功能。这样的复杂性可能导致代码难以维护、理解和使用。外观模式…

NSQ 实现逻辑探秘

1 什么是 NSQ NSQ 是一个消息队列中间件&#xff0c;用 go 实现&#xff0c;有如下特点&#xff1a; 分布式&#xff1a; 它提供了分布式的、去中心化且没有单点故障的拓扑结构&#xff0c;稳定的消息传输发布保障&#xff0c;能够具有高容错和高可用特性。 易于扩展&#xf…

【Echarts】echarts饼图、圆环图配置代码详解

前言 简介&#xff1a;本文将从头开始&#xff0c;带你快速上手 echarts最常用图例—饼图 准备&#xff1a;请自行先将echarts图例引入你的项目&#xff0c;本文不多介绍。&#xff08;引入 echarts教程&#xff1a;http://t.csdn.cn/mkTa4&#xff09; 心得&#xff1a;echar…

递归函数:

含义&#xff1a;自己调自己 递归三要素&#xff1a;定义函数、终止条件和等价关系式 小案例&#xff1a;排序 let arr1 [8, 8, 9, 13, 45, 8, 0, 1, 9, 66];//定义函数function quickSort(arr) {//终止条件if (arr.length < 1) return arr;const baseIndex Math.floor(…

十五、docker学习-docker核心docker数据卷

什么是数据卷 当我们在使用docker容器的时候&#xff0c;会产生一系列的数据文件&#xff0c;这些数据文件在我们删除docker容器时是会消失的&#xff0c;但是其中产生的部分内容我们是希望能够把它给保存起来另作用途的&#xff0c;Docker将应用与运行环境打包成容器发布&…

【游戏逆向】D3D HOOK实现透视讲解

实现目的: 目前大部分游戏通过Direct3D实现3D效果,通过挂钩相应函数,可以实现3D透视,屏幕挂字效果。而透视,屏蔽特定效果,设置透明在很多游戏(特别是FPS)中发挥着巨大的作用! 实现思路: [D3D] DirectX的功能都是以COM组件的形式提供的。在Direct3D中,主要通过采…

Unity新输入系统

1、导入新输入系统 &#xff08;1&#xff09; 这里改成.NET Framework&#xff0c;下面改成input system package(New) 2、使用新系统 &#xff08;1&#xff09; 在你的player物体上添加Player Input组件&#xff0c;然后CreateAction &#xff08;2&#xff09; 创建出…

连接器信号完整性仿真教程 五

本文将详细介绍CST电磁仿真的激励源&#xff08;Excitation Source&#xff09;及其设置。CST微波工作室根据具体应用和结构类型提供多种不同的激励源&#xff0c;总得来说包含激励端口&#xff08;Excitation Port&#xff09;和场源&#xff08;Field Sources&#xff09;。 …

3.Mysql子查询练习

1.子查询概述 子查询指一个查询语句嵌套在另一个查询语句内部的查询&#xff0c;内部的查询是外部查询的条件&#xff0c;这个特性从MySQL4.1开始引入 子查询(内查询)在主查询之前执行完成 子查询的结果被主查询(外查询)使用 注意事项&#xff1a; 子查询要包含在括号内 将子查…