设计模式 代理模式(Proxy Pattern)

简绍

代理模式是一种结构型设计模式,它允许您提供一个替代对象(代理)来控制对一个真实对象的访问。这种模式通常用于在访问某个对象之前或之后执行一些额外的操作,比如缓存、日志记录、权限验证等

静态代理

静态代理的特点
  • 代理类和真实主题类在编译时确定:
    代理类和真实主题类都是在编写代码时就确定的,并且它们通常共享一个公共接口。
  • 代理类包含对真实主题类的引用:
    代理类包含一个对真实主题类实例的引用,并通过该引用调用真实主题的方法。
  • 代理类可以执行额外的操作:
    代理类可以在调用真实主题的方法之前或之后执行一些额外的操作,如日志记录、权限验证等。
静态代理的应用场景
  • 日志记录:
    在调用方法前后记录日志。
  • 权限验证:
    在调用方法前验证用户权限。
  • 缓存:
    缓存方法的结果,以避免重复计算。
  • 事务管理:
    在调用方法前后管理事务。
  • 性能优化:
    延迟加载或异步处理。
静态代理的缺点

静态代理的一个缺点是,对于每一个真实主题类,都需要创建一个代理类。在需要为多个类创建代理的情况下,这可能会导致大量的代理类。

创建基础调用类

public interface Mobile {void buy(String mobileName);
}

原有的实现方式

public class XiaomiProxy implements Mobile{@Overridepublic void buy(String mobileName) {System.out.println("购买到手一台: "+ mobileName);}
}

通过对原有基础类调用 ,封装之前的调用逻辑,在上面附加一层处理方式

public class PayProxy implements Mobile{private XiaomiProxy xiaomiProxy;public PayProxy(){xiaomiProxy = new XiaomiProxy();}@Overridepublic void buy(String mobileName) {System.out.println("准备刷卡购买: " + mobileName);xiaomiProxy.buy(mobileName);}
}

两种 结果, 我们可以在代理类中去进行各种数据处理

public class Main {public static void main(String[] args) {XiaomiProxy xiaomiProxy = new XiaomiProxy();xiaomiProxy.buy("小米4");System.out.println("-----------------------");PayProxy payProxy = new PayProxy();payProxy.buy("小米4");}
}

jdk动态代理

JDK 动态代理是一种在运行时生成代理类的技术,它允许您为任何实现了接口的类创建代理对象。与静态代理不同,动态代理不需要显式地编写代理类的代码,而是通过 Java 反射 API 自动生成代理类。这种方式更加灵活,因为代理类是在运行时动态生成的。

JDK 动态代理的工作原理
  • 创建 InvocationHandler 实现类:
    创建一个实现了 InvocationHandler 接口的类,该类将定义代理行为。
  • 创建代理对象:
    使用 java.lang.reflect.Proxy 类的 newProxyInstance 方法创建代理对象。
  • 调用代理对象的方法:
    通过代理对象调用方法,实际执行的是 InvocationHandler 中定义的方法。
JDK 动态代理的关键组件
  • Interface (接口):
    代理对象和真实主题对象必须实现相同的接口。
  • RealSubject (真实主题):
    这是实际执行请求的对象。
  • InvocationHandler:
    这是一个接口,它的实现类定义了代理行为。
  • Proxy (代理):
    由 java.lang.reflect.Proxy 类创建的代理对象。
invoke 方法签名

invoke 方法是 Java 反射机制中的一个核心部分,特别是在使用动态代理时。它定义在 java.lang.reflect.InvocationHandler 接口中,并且在代理对象的方法被调用时由 Java 虚拟机 (JVM) 自动触发

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  • 这是调用该 invoke 方法的代理实例对象。
    当客户端通过代理对象调用一个方法时,这个方法实际上会委托给 InvocationHandler 中的 invoke 方法。该参数可以用于获取代理对象的信息,例如使用 proxy.getClass().getName()。有时可以将代理对象返回以进行连续调用,即链式调用,因为 this 并不是代理对象本身。

  • method:
    这是在代理实例上调用的接口方法对应的 Method 实例。
    它包含了关于要调用的方法的信息,如方法名、参数类型、返回类型等。
    可以使用这个参数来决定如何处理方法调用,例如是否需要执行某些预处理或后处理逻辑。

  • args:
    这是一个 Object 数组,包含了在代理对象上调用方法时传递的参数。
    参数按照方法定义的顺序排列。
    如果方法没有参数,则此数组为空。

public class PayProxyHandle implements InvocationHandler {private final Object target;public PayProxyHandle(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(target, args);}
}
Proxy.newProxyInstance 方法被用来创建一个动态代理对象。以下是该方法调用的详细解释:
获取类加载器

Mobile.class.getClassLoader()
这一行获取了 Image 接口的类加载器。类加载器是负责加载类到 JVM 的对象。在大多数情况下,您可以使用类的 getClassLoader() 方法来获取类加载器,但是基本类型和 Object 类没有类加载器,它们是由特殊的 Bootstrap ClassLoader 加载的。对于普通的类和接口,通常可以使用 .class 字段来获取其类加载器。

指定接口

ew Class<?>[]{Mobile.class}
这一行创建了一个接口数组,这里只有一个接口 Image。Proxy.newProxyInstance 方法需要知道代理对象需要实现哪些接口。

提供 InvocationHandler

new PayProxyHandle(xiaomiProxy)
这一行创建了一个 PayProxyHandle 类的实例,它实现了 InvocationHandler 接口。InvocationHandler 接口定义了一个 invoke 方法,该方法会在代理对象的方法被调用时被调用。DynamicProxyHandler 类需要处理代理对象上发生的所有方法调用,并根据需要执行额外的逻辑。

public class Main {public static void main(String[] args) {Mobile o =(Mobile) Proxy.newProxyInstance(Mobile.class.getClassLoader(),new Class<?>[]{Mobile.class},new PayProxyHandle(xiaomiProxy));o.buy("小米4");}
}

JDK 动态代理是一种强大的工具,它允许您在不修改现有类的情况下为现有类添加新的行为。通过使用动态代理,您可以轻松地扩展系统的功能,同时保持代码的整洁和模块化。动态代理的一个主要优势是无需为每个真实主题类创建一个代理类,因此可以减少代码量并提高灵活性。

cglib代理。

CGLIB(Code Generation Library)是一种强大的、高性能且动态的字节码生成库。它可以在运行时创建一个指定类的新子类。这种能力使得 CGLIB 成为 AOP(面向切面编程)框架和其他需要在运行时动态创建子类的应用的理想选择。CGLIB 不依赖于接口,因此它可以用于代理没有实现接口的类。

CGLIB 代理的工作原理
  • 代理类生成:
    CGLIB 使用字节码技术在运行时动态生成代理类。
    生成的代理类是目标类的一个子类,并且覆盖了目标类的所有非最终方法。
    这意味着所有非最终方法都可以被拦截并执行额外的逻辑。
  • 方法拦截:
    CGLIB 提供了一个 MethodInterceptor 接口,该接口定义了一个 intercept 方法。
    每当代理类的方法被调用时,intercept 方法就会被触发。
    在 intercept 方法内,你可以执行预处理和后处理逻辑,以及调用原始方法。
  • 代理对象创建:
    代理对象通过 CGLIB 的 Enhancer 类创建。你提供一个 Callback 或者实现 MethodInterceptor 的对象,该对象定义了如何处理方法调用。
CGLIB 的基本使用步骤
  • 导入 CGLIB 相关依赖:
    • 如果你使用 Maven 或 Gradle,需要添加 CGLIB 的依赖到你的项目中。
  • 定义目标类:
    定义一个目标类,该类不需要实现任何接口。
  • 创建 MethodInterceptor:
    • 实现 MethodInterceptor 接口,定义 intercept 方法。
    • 在 intercept 方法中,你可以执行一些预处理操作,然后调用原始方法,最后执行后处理操作。
  • 创建代理对象:
    • 使用 CGLIB 的 Enhancer 类来创建代理对象。
    • 设置目标类、回调函数等信息。
  • 使用代理对象:
    • 使用生成的代理对象代替原始对象。

CGLIB 的优缺点

  • 优点:
    支持对所有方法的拦截,即使这些方法没有声明为 public。
    不需要目标类实现特定接口,适用于没有实现接口的类。
    高性能,尤其是在多次调用相同方法的情况下。
  • 缺点:
    对于最终类 (final) 和最终方法 (final) 无法创建代理。
    由于使用了字节码生成技术,可能会产生额外的 CPU 和内存开销。

安装 CGLIB

<dependency><groupId>net.sf.cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
intercept 方法的参数解释

intercept 方法的签名如下:

Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
  • obj: 代理对象本身。这是 CGLIB 动态生成的代理类的实例。
  • method: java.lang.reflect.Method 类型的对象,表示正在被调用的方法。
  • args: 方法调用时传递的实际参数数组。
  • proxy: MethodProxy 类型的对象,用于调用目标方法。
使用 MethodProxy 调用目标方法

MethodProxy 提供了两种主要的方式来调用目标方法:

使用 invokeSuper 方法:

Object result = proxy.invokeSuper(obj, args);
这种方式会直接调用代理对象的父类(即目标类)的方法。

使用 invoke 方法:

Object result = proxy.invoke(obj, args);
这种方式也会调用目标方法,但它会经过 CGLIB 的代理机制。
通常情况下,建议使用 invokeSuper 方法,因为它更高效并且避免了潜在的无限递归问题。

public class TargetMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object result = methodProxy.invokeSuper(o, objects);return result;}
}

在 CGLIB 中,Enhancer 类是用来创建动态代理的工具类。

// 创建 Enhancer 实例
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(TargetClass.class);
// 设置 Callback
enhancer.setCallback(new TargetMethodInterceptor());
// 创建代理对象
TargetClass proxy = (TargetClass) enhancer.create();
// 使用代理对象
proxy.test("test");

CGLIB 是一种强大的工具,可以用于创建动态代理,尤其是在不支持 JDK 动态代理的场景下(即目标类没有实现接口)。它通过字节码生成技术在运行时创建代理类,并且能够拦截所有的非最终方法。CGLIB 代理广泛应用于 AOP 框架中,例如 Spring AOP。

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

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

相关文章

OpenCV小练习:身份证号码识别

目标&#xff1a;针对一张身份证照片&#xff0c;把身份证号码识别出来&#xff08;转成数字或字符串&#xff09;。 实现思路&#xff1a;需要将目标拆分成两个子任务&#xff1a;(1) 把身份证号码区域从整张图片中检测/裁剪出来&#xff1b;(2) 将图片中的数字转化成文字。第…

Java重修笔记 第四十五天 LinkedHashSet 类

LinkedHashSet 类 1. LinkedHashSet 是 HashSet 的子类&#xff0c;继承 HashSet 的方法 2. LinkedHashSet 的底层是 LinkedHashMap &#xff0c;底层维护了一个数组加双向链表的组合 3. LinkedHashSet 根据元素的 hashCode 值来决定元素在 table 数组上的存储位置&#xf…

Pandas库性能优化指南:从基础到进阶(终)

Pandas是Python中广泛使用的数据处理库&#xff0c;凭借其强大的功能和易用性&#xff0c;深受数据科学家和开发者的青睐。然而&#xff0c;Pandas在处理大规模数据时可能会遇到性能瓶颈&#xff0c;导致执行效率低下。本文将深入探讨如何通过一系列优化技巧&#xff0c;提升Pa…

快速学习go-zero

go的web框架有很多,目前go的社区大家对于框架的态度也不尽相同,有些轻量级的框架,但是也就代表整合第三方中间件就需要自己根据客户端进行封装,比如gingorm,也有些功能完全但是被认为丢失了go本身轻量设计的初衷, 比如goframe,而同样的微服务有很多框架,国内比较出门的就是go-z…

rockyliunx 救援模式下禁用docker

目录地址 /usr/lib/systemd/system/docker.service 进入系统界面&#xff1a; 选择系统 按E 按e出现 如下界面&#xff0c;找到 quite 后面添加 init/bin/bash 按 ctrl x 保存 后&#xff0c;到如下界面 加载文件系统为读写 输入命令 mount -o remount, rw / 修改docer.s…

docker的安装+docker镜像的基本操作

一&#xff0e;docker的介绍 1、Docker 是什么&#xff1f; Docker 是⼀个开源的应⽤容器引擎&#xff0c;可以实现虚拟化&#xff0c;完全采⽤“沙 盒”机制&#xff0c;容器之间不会存在任何接⼝。 Docker 通过 Linux Container&#xff08;容器&#xff09;技术将任意…

SpringBoot项目集成数据脱敏(密码加密)功能

代码连接【https://gitee.com/pengmqqq/sensitive-data-encryption】 介绍 后端敏感数据加密的一些解决方案&#xff0c;包括&#xff1a; 配置文件敏感数据加解密前端传输敏感数据加解密数据库获取的敏感数据加解密 软件架构 配置文件数据脱敏&#xff1a; Jasypt AES …

【线程池】

什么是线程池&#xff1f; 线程池是一个可以复用线程的技术。简单来说&#xff0c;线程池是一种基于池化技术的思想来管理线程的技术&#xff0c;旨在减少线程的创建和销毁次数&#xff0c;提高系统的响应速度和吞吐量。它预先创建了一定数量的线程&#xff0c;并将这些线程放…

力扣52-最大子序和(java详细题解)

题目链接&#xff1a;https://leetcode.cn/problems/maximum-subarray/description/ 前情提要&#xff1a; 因为本人最近都来刷贪心类的题目所以该题就默认用贪心方法来做。 贪心方法&#xff1a;局部最优推出全局最优。 如果一个题你觉得可以用局部最优推出全局最优&#…

Java中的定时器(Timer)

目录 一、什么是定时器? 二、标准库中的定时器 三、实现自定义定时器 一、什么是定时器? 定时器就像一个"闹钟"&#xff0c;当它到达设定的时间后&#xff0c;就会执行预定的代码。 例如&#xff0c;我们在TCP的超时重传机制中讲过&#xff0c;如果服务器在规定…

DNS劫持问题

目录 DNS劫持概述 定义 图示 ​编辑图示说明 DNS劫持的原理 1. DNS请求与响应过程 图示 ​编辑2. 劫持发生点 本地劫持 路由器劫持 中间人攻击 图示 ​编辑图示说明 DNS劫持的影响 1. 对个人用户的影响 图示 ​编辑图示说明 2. 对企业的影响 图示 ​编辑图示…

【Python】set os.environ[“CUDA_VISIBLE_DEVICES“] = ‘1‘ Invalid

If set os.environ[“CUDA_VISIBLE_DEVICES”] ‘1’ Invalid you can place the code block os.environ["CUDA_VISIBLE_DEVICES"] 1 before all cuda code calls. For example: import os os.environ["CUDA_VISIBLE_DEVICES"] 2 import numpy as np…

0828作业+梳理

一、作业 代码&#xff1a; #include <iostream>using namespace std;using datatype int; //类型重命名 #define MAX 2 //宏定义 //结构体定义 struct Sqlist { private:datatype *data; //顺序表数组int size 0; //数组大小int len 0; …

Ubuntu 16.04下Firefox版本更新

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; 在Ubuntu 16.04上更新Firefox的过程可能涉及多个步骤&#xff0c;具体取决于你的需求&#xff0c;比如是要安装一个稳定版本&#xff0c;还是需要使用最新的开发者版本或beta版本。下面我将详细介绍如何在Ub…

Python酷库之旅-第三方库Pandas(105)

目录 一、用法精讲 456、pandas.DataFrame.rdiv方法 456-1、语法 456-2、参数 456-3、功能 456-4、返回值 456-5、说明 456-6、用法 456-6-1、数据准备 456-6-2、代码示例 456-6-3、结果输出 457、pandas.DataFrame.rtruediv方法 457-1、语法 457-2、参数 457-3…

搭建面向切面编程项目

此项目在整合Mybatis基础上修改&#xff0c;可参考主页的整合Mybatis文章 注解版本 第一步 引入maven坐标 <!-- 切面编程所需jar包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId>…

学生管理系统升级(登录注册 + 关联学生管理系统)

新增需求 这是在昨天的基础初代版本上面新增一个登录注册忘记密码的功能 需求分析 注册 登录 忘记密码 user类代码呈现 package StudentSystem;public class User {private String username;private String password;private String personID;private String phoneNumber;pu…

PHP同城派送多区域运营配送小程序源码

&#x1f69a;&#x1f4a8;「同城派送多区域运营小程序」——让每一份需求快速触达&#xff01;&#x1f308;&#x1f680; &#x1f525; 开篇燃爆&#xff1a;同城生活新风尚&#xff0c;一键速达不是梦&#xff01; Hey小伙伴们&#xff0c;你还在为找不到合适的同城服务…

推荐并整理一波vscode插件(哪些内置了,哪些好用)

文章目录 背景现在还在用的&#xff08;21款&#xff09;Chinese(Simplified)简体中文Chinese LoremLorem ipsumCode Runner&#xff08;很推荐&#xff09;Codeium: AI Coding Autocomplete&#xff08;推荐&#xff09;Draw.io IntegrationESLintHighlight Matching TagJavaS…