结构型模式-代理模式

news2024/11/16 6:02:55

1.概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

2.结构

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

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

3.静态代理

【例】火车站卖票

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:
在这里插入图片描述
代码如下

package com.itheima.pattern.proxy.static_proxy;

/**
 * @program: design-patterns
 * @interfaceName SellTickets
 * @description: 卖火车票的接口
 * @author: 
 * @create: 2023-01-15 10:45
 * @Version 1.0
 **/
public interface SellTickets {
    void sell();
}
package com.itheima.pattern.proxy.static_proxy;

/**
 * @program: design-patterns
 * @ClassName TrainStation
 * @description: 火车站类-被代理类
 * @author: 
 * @create: 2023-01-15 10:46
 * @Version 1.0
 **/
public class TrainStation implements SellTickets {

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}
package com.itheima.pattern.proxy.static_proxy;

/**
 * @program: design-patterns
 * @ClassName ProxyPoint
 * @description: 代售点类-代理类
 * @author: 
 * @create: 2023-01-15 10:46
 * @Version 1.0
 **/
public class ProxyPoint implements SellTickets {

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

    @Override
    public void sell() {
        System.out.println("代售点收取一些服务费用");
        trainStation.sell();
    }
}
package com.itheima.pattern.proxy.static_proxy;

/**
 * @program: design-patterns
 * @ClassName Client
 * @description: 测试类
 * @author: 
 * @create: 2023-01-15 10:49
 * @Version 1.0
 **/
public class Client {
    public static void main(String[] args) {
        // 创建代售点类对象
        ProxyPoint proxyPoint = new ProxyPoint();
        // 调用方法进行买票
        proxyPoint.sell();
    }
}

从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)

4.JDK动态代理

接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。
代码如下

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @program: design-patterns
 * @interfaceName SellTickets
 * @description: 卖火车票的接口
 * @author: 
 * @create: 2023-01-15 10:45
 * @Version 1.0
 **/
public interface SellTickets {
    void sell();
}
package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @program: design-patterns
 * @ClassName TrainStation
 * @description: 火车站类
 * @author: 
 * @create: 2023-01-15 10:46
 * @Version 1.0
 **/
public class TrainStation implements SellTickets {

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}
package com.itheima.pattern.proxy.jdk_proxy;

import java.lang.reflect.Proxy;
/**
 * @program: design-patterns
 * @ClassName ProxyFactory
 * @description: 代理工厂类
 * @author: 
 * @create: 2023-01-15 10:54
 * @Version 1.0
 **/
public class ProxyFactory {

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

    /**
     * ClassLoader loader:类加载器,用于加载代理类,可以通过目标对象获取类加载
     * Class<?>[] interfaces:代理类实现的接口的字节码对象
     * InvocationHandler:代理对象的调用处理程序
     *
     * @return 获取代理对象
     */
    public SellTickets getProxyObj() {
        // 返回代理对象
        SellTickets proxyObj = (SellTickets) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                /**
                 * proxy:代理对象,和proxyObj对象是同一个对象,在invoke方法中基本不用
                 * method:对接口中的方法进行封装的method对象
                 * args:调用方法的实际参数
                 * return:方法的返回值
                 */
                (proxy, method, args) -> {
                    System.out.println("代售点收取一定的服务费用(jdk动态代理)");
                    return method.invoke(station, args);
                });
        return proxyObj;
    }
}
package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @program: design-patterns
 * @ClassName Client
 * @description: 客户类
 * @author: zhangzhifeng
 * @create: 2023-01-15 11:03
 * @Version 1.0
 **/
public class Client {
    public static void main(String[] args) {
        // 获取代理对象
        // 1.创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        // 2.使用factory对象的方法获取代理对象
        SellTickets proxyObj = factory.getProxyObj();
        // 3.调用卖电脑的方法
        proxyObj.sell();
    }
}

使用了动态代理,我们思考下面问题:

  • ProxyFactory是代理类吗?
    ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:
package com.sun.proxy;
import com.itheima.proxy.dynamic.jdk.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 invocationHandler) {
		super(invocationHandler);
	}
	static {
		try {
			m1 = Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));
			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
			m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
			m0 =Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
			return;
		} 
		catch (NoSuchMethodException noSuchMethodException) {
			throw new NoSuchMethodError(noSuchMethodException.getMessage());
		}
		catch (ClassNotFoundException classNotFoundException) {
			throw new NoClassDefFoundError(classNotFoundException.getMessage());
		}
	}
	public final boolean equals(Object object) {
		try {
			return (Boolean)this.h.invoke(this, m1, new Object[]{object});
		}
		catch (Error | RuntimeException throwable) {
			throw throwable;
		}
		catch (Throwable throwable) {
			throw new UndeclaredThrowableException(throwable);
		}
	}
	public final String toString() {
		try {
			return (String)this.h.invoke(this, m2, null);
		}
		catch (Error | RuntimeException throwable) {
			throw throwable;
		}
		catch (Throwable throwable) {
			throw new UndeclaredThrowableException(throwable);
		}
	}
	public final int hashCode() {
		try {
			return (Integer)this.h.invoke(this, m0, null);
		}
		catch (Error | RuntimeException throwable) {
			throw throwable;
		}
		catch (Throwable throwable) {
			throw new UndeclaredThrowableException(throwable);
		}
	}
	public final void sell() {
		try {
			this.h.invoke(this, m3, null);
			return;
		}
		catch (Error | RuntimeException throwable) {
			throw throwable;
		}
		catch (Throwable throwable) {
			throw new UndeclaredThrowableException(throwable);
		}
	}
}

从上面的类中,我们可以看到以下几个信息:

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

动态代理的执行流程是什么样?
下面是摘取的重点代码:

//程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
	private static Method m3;
	public $Proxy0(InvocationHandler invocationHandler) {
		super(invocationHandler);
	}
	static {
		m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
	}
	public final void sell() {
		this.h.invoke(this, m3, null);
	}
}
//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
	protected InvocationHandler h;
	protected Proxy(InvocationHandler h) {
		this.h = h;
	}
}
//代理工厂类
public class ProxyFactory {
	private TrainStation station = new TrainStation();
	public SellTickets getProxyObject() {
		SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
		station.getClass().getInterfaces(),
		new InvocationHandler() {
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
				Object result = method.invoke(station, args);
				return result;
			}
		});
		return sellTickets;
	}
}
//测试访问类
public class Client {
	public static void main(String[] args) {
		//获取代理对象
		ProxyFactory factory = new ProxyFactory();
		SellTickets proxyObject = factory.getProxyObject();
		proxyObject.sell();
	}
}

执行流程如下:

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

5.CGLIB动态代理

同样是上面的案例,我们再次使用CGLIB代理实现。

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
     <version>3.3.0</version>
</dependency>

代码如下

package com.itheima.pattern.proxy.cglib_proxy;

/**
 * @program: design-patterns
 * @ClassName TrainStation
 * @description: 火车站类
 * @author: 
 * @create: 2023-01-15 10:46
 * @Version 1.0
 **/
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
    
}
package com.itheima.pattern.proxy.cglib_proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @program: design-patterns
 * @ClassName ProxyFactory
 * @description: 代理对象工厂,用来获取代理对象
 * @author: 
 * @create: 2023-01-15 13:10
 * @Version 1.0
 **/
public class ProxyFactory implements MethodInterceptor {

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

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

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//        System.out.println("intercept 方法执行了");
        System.out.println("代售点收取一定的服务费用(CGLib)");
        // 要调用目标对象的方法
        Object obj = method.invoke(station, objects);
        return obj;
    }
}
package com.itheima.pattern.proxy.cglib_proxy;

/**
 * @program: design-patterns
 * @ClassName Client
 * @description: 客户端测试类
 * @author: 
 * @create: 2023-01-15 13:17
 * @Version 1.0
 **/
public class Client {
    public static void main(String[] args) {
        // 创建代理工厂类对象
        ProxyFactory factory = new ProxyFactory();
        // 获取代理类对象
        TrainStation proxyObj = factory.getProxyObj();
        // 调用代理对象中的sell方法卖票
        proxyObj.sell();
    }
}

6.三种代理模式对比

  • 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)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

7.优缺点

  • 优点
    (1)代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
    (2)代理对象可以扩展目标对象的功能
    (3)代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
  • 缺点
    (1)增加了系统的复杂度

8.使用场景

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

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

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

相关文章

Nacos 配置中心源码讲解

目录 1. 配置中心的优点 2. 配置模型结构 3. 配置中心 Server 端实现原理 3.1 新建配置 / 发布配置 3.2 查询配置 4. 配置中心 Client 端实现原理 4.1 发布配置 4.2 查询配置 4.3 监听机制 Listener 1. 配置中心的优点 运行时动态修改系统参数配置&#xff0c;不用重启…

排序算法解析:快排,归并 (全)

一、快排原始快排 算法思想&#xff1a;ps&#xff1a;排序的效果其实就是使一个数列中的每个数都满足左边数比它小、右边数比它大&#xff08;假设升序&#xff09;。接下来我们来了解快排&#xff1a;多次递归遍历&#xff0c;每单次遍历&#xff0c;设定一个限定值&#xff…

02 |「数据结构、逻辑结构、物理结构」基本概念简析

前言 前言&#xff1a;简析数据结构、逻辑结构、物理结构。 文章目录前言一、数据结构1. 简介2. 数据3. 结构4. 分析5. 分类1&#xff09;线性结构&#xff08;线性表&#xff09;2&#xff09;树结构3&#xff09;图结构二、逻辑结构与物理结构1. 为什么要有逻辑结构和物理结构…

SpringBoot+Vue--前端搭建-笔记1

前端搭建 首先安装node.js(百度) 官网下载地址&#xff1a;http://nodejs.cn/download 以前写的关于npm 后端了解的npm_biubiubiu0706的博客-CSDN博客 安装Node.js淘宝镜像加速器(cnpm) npm install cnpm -g(可以不安装) #建议使用如下语句解决npm速度慢的问题 好比设置仓…

代码随想录算法训练营三期 day 24 - 回溯 (1) (补)

回溯算法理论基础 什么是回溯法 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。所以以下讲解中&#xff0c;回溯函数也就是递归函数&#xff0c;指的都是一个函数。 回溯法的效率 回溯的本质是穷举&…

【手把手教你学51单片机】中断的优先级

注&#xff1a;本文章转载自《手把手教你学习51单片机》&#xff01;因转载需要原文链接&#xff0c;故无法选择转载&#xff01; 如若侵权&#xff0c;请联系我进行删除&#xff01;上传至网络博客目的为了记录自己学习的过程的同时&#xff0c;同时能够帮助其他一同学习的小伙…

第四十三章 动态规划——最长单调序列模型

第四十三章 动态规划——最长单调序列模型一、最长单调序列模型1、模型母题2、思路分析&#xff08;两种方法&#xff1a;DP&#xff0c;贪心&#xff09;二、模型的应用1、AcWing 1017. 怪盗基德的滑翔翼&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析&#xff…

C规范编辑笔记(十四)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) C规范编辑笔记(九) C规则编辑笔记(十) C规范编辑笔记(十一) C规范编辑笔记(十二) C规范编辑笔记(…

Linux进程学习【一】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Perseverance is not a long race; it is many short races one after another…

Linux基本功系列之rename命令实战

文章目录一. rename 命令介绍二. 语法格式及常用选项三. 参考案例3.1 将当前目录下所有.cfg的文件&#xff0c;替换为.txt结尾3.2 将所有出现mufeng的部分都替换为mufeng13.3 将mufeng0开头都变成mufeng00开头3.4 rename支持正则表示式总结前言&#x1f680;&#x1f680;&…

2023-1-22 刷题情况

积水面积 先祝大家新年快乐&#xff0c;新的一年&#xff0c;万事如意。 题目描述 一组正整数&#xff0c;分别表示由正方体叠起的柱子的高度。若某高度值为 xxx&#xff0c;表示由 xxx 个正立方的方块叠起&#xff08;如下图&#xff0c;0≤x≤50000 \le x \le 50000≤x≤5…

卷积神经网络进阶--基础知识

卷积神经网络进阶 b站课程链接碳基生物都能学会的神经网络&#xff08;跳着看的&#xff09; 因为我用的是pytorch&#xff0c;而该课程是用tenserflow的&#xff0c;所以主要记了一下理论 为什么要讲不同的网络结构 不同的网络结构解决的问题不同不同的网络结构使用的技巧不同…

【人工智能原理自学】卷积神经网络:打破图像识别的瓶颈

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文讲解卷积神经网络&#xff1a;打破图像识别的瓶颈&#xff0c;一起卷起来叭&#xff01; 目录一、手写体识别二、“炼丹”一、手写体识别 在机器学习、神经网络领域&#…

【数据分析】(task4)数据可视化

note matplotlib的四个容器&#xff1a; Figure&#xff1a;顶层级&#xff0c;用来容纳子 Axes&#xff0c;一组具体绘图元素和画布&#xff08;canvas&#xff09;。 画板。Axes&#xff1a;matplotlib宇宙的核心&#xff0c;容纳了大量元素用来构造一幅幅子图&#xff0c;一…

【QT5.9】与MFC对比学习笔记-感悟篇【2023.01.22】

简介 在公司从事MFC的程序维护一年两个月&#xff0c;期间因为公司被QT告侵权对QT产生了抵触的心情。现在无奈要用到&#xff0c;需要抓紧学习了。 正文 1.数据模型 先说下刚用到的模型&#xff0c;模型也叫数据模型&#xff0c;也就是耳熟的MVC架构中的M&#xff08;Model…

我用笨办法啃下了一个开源项目的源码!

目录 1、从最简单的源码开始&#xff1a;别幻想一步登天 2、循序渐进&#xff1a;先搞定底层依赖的技术 3、一定要以Hello World作为入口来阅读 4、抓大放小&#xff0c;边写注释边画图 5、反复三遍&#xff0c;真正理解源码 6、借力打力&#xff0c;参考源码分析书籍及博客 7…

研一寒假C++复习笔记--引用的使用

​​​​​​​ 目录 1--引用的基本语法 2--引用的注意事项 3--在函数参数中使用引用 4--引用作函数的返回值 5--引用的本质 6--常量引用 1--引用的基本语法 引用相当于给变量起别名&#xff0c;其基本语法如下&#xff1a; 数据类型 &别名 原名 # include <…

Linux操作系统之进程信号

代码存放在&#xff1a;https://github.com/sjmshsh/System-Call-Learn/tree/master/signal 我们先来看一张图&#xff0c;了解一下通过阅读本博客&#xff0c;你可以收获什么。 背景知识 首先我说明一点 信号 ! 信号量 我们这篇文章讲解的是信号&#xff0c;不是信号量 信…

POJ3263. Tallest Cow题解(c++ 前缀和)

POJ3263. Tallest Cow 传送门&#xff1a;Tallest Cow 题目&#xff1a; 有N头牛站成一行。两头作能够相支看见&#xff0c;当且仅当它们中间的牛身高都比它们矮。现在&#xff0c;我们只知道其中最高的牛是第P头&#xff0c;它的身高是H&#xff0c;不知道剩余N-1头牛的身高。…

大数据之Kafka高级知识点

文章目录前言一、分片和副本机制&#xff08;一&#xff09;分片机制&#xff08;二&#xff09;副本二、Kafka如何保证数据不丢失&#xff08;一&#xff09;Producer生产者&#xff08;二&#xff09;Broker&#xff08;三&#xff09;Consumer消费者三、消息存储和查询机制总…